From 6d7a0f2a5b3693d761a603117870769700965402 Mon Sep 17 00:00:00 2001 From: Salem Yaslem Date: Fri, 12 Feb 2021 19:04:42 +0300 Subject: [PATCH] make window selection, area selection functional --- Cargo.lock | 95 ++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/area_capture.rs | 90 ++++++++++++++++++++++++++++++++++++++ src/ffmpeg_interface.rs | 77 +++++++++++++++++++-------------- src/main.rs | 38 ++++++++++++++--- windows/ui.glade | 4 +- 6 files changed, 262 insertions(+), 44 deletions(-) create mode 100644 src/area_capture.rs diff --git a/Cargo.lock b/Cargo.lock index 2f68ae2..1096985 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,15 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.38" @@ -38,6 +47,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + [[package]] name = "bitflags" version = "1.2.1" @@ -48,10 +63,12 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" name = "blue-recorder" version = "0.1.0" dependencies = [ + "chrono", "gdk", "gio", "glib", "gtk", + "regex", "rust-ini", ] @@ -99,6 +116,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "dlv-list" version = "0.2.2" @@ -282,7 +312,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -460,6 +490,25 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.5.2" @@ -631,6 +680,24 @@ dependencies = [ "rand_core", ] +[[package]] +name = "regex" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" + [[package]] name = "rust-ini" version = "0.16.1" @@ -717,6 +784,26 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "toml" version = "0.5.8" @@ -756,6 +843,12 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index dd52cf9..3fae498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,8 @@ edition = "2018" gdk = "0.13.2" glib = "0.10.3" rust-ini = "0.16" +regex = "1.4.3" +chrono = "0.4.19" [dependencies.gtk] version = "0.9.0" diff --git a/src/area_capture.rs b/src/area_capture.rs new file mode 100644 index 0000000..fddddb7 --- /dev/null +++ b/src/area_capture.rs @@ -0,0 +1,90 @@ +extern crate regex; +use regex::Regex; +use std::process::Command; + +// this struct use "xwininfo" to get area x, y, width and height +#[derive(Debug, Copy, Clone)] +pub struct AreaCapture { + pub x: u16, + pub y: u16, + pub width: u16, + pub height: u16, +} + +impl AreaCapture { + pub fn new() -> AreaCapture { + AreaCapture { + x: 0, + y: 0, + width: 0, + height: 0, + } + } + + pub fn get_area(&mut self) -> Self { + let coordinate = xwininfo_to_coordinate( + String::from_utf8(Command::new("xwininfo").output().unwrap().stdout).unwrap() + ); + self.x = coordinate.0; + self.y = coordinate.1; + self.width = coordinate.2; + self.height = coordinate.3; + *self + } + + pub fn get_window_by_name(&mut self, name: &str) -> Self { + let coordinate = xwininfo_to_coordinate( + String::from_utf8(Command::new("xwininfo").arg("-name").arg(name).output().unwrap().stdout).unwrap(), + ); + self.x = coordinate.0; + self.y = coordinate.1; + self.width = coordinate.2; + self.height = coordinate.3; + *self + } +} + +fn xwininfo_to_coordinate(xwininfo_output: String) -> (u16, u16, u16, u16) { + let x: u16 = Regex::new(r"A.*X:\s+(\d+)\n") + .unwrap() + .captures(xwininfo_output.as_str()) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string() + .parse::() + .unwrap(); + let y: u16 = Regex::new(r"A.*Y:\s+(\d+)\n") + .unwrap() + .captures(xwininfo_output.as_str()) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string() + .parse::() + .unwrap(); + let width: u16 = Regex::new(r"Width:\s(\d+)\n") + .unwrap() + .captures(xwininfo_output.as_str()) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string() + .parse::() + .unwrap(); + let height: u16 = Regex::new(r"Height:\s(\d+)\n") + .unwrap() + .captures(xwininfo_output.as_str()) + .unwrap() + .get(1) + .unwrap() + .as_str() + .to_string() + .parse::() + .unwrap(); + + (x, y, width, height) +} diff --git a/src/ffmpeg_interface.rs b/src/ffmpeg_interface.rs index 2cdba6d..71a73c0 100644 --- a/src/ffmpeg_interface.rs +++ b/src/ffmpeg_interface.rs @@ -1,8 +1,12 @@ +use gtk::{ + CheckButton, ComboBoxExt, ComboBoxText, Entry, EntryExt, FileChooser, FileChooserExt, + SpinButton, SpinButtonExt, ToggleButtonExt, +}; use std::path::PathBuf; use std::process::Command; use std::thread::sleep; use std::time::Duration; -use gtk::{CheckButton, SpinButton, ComboBoxText, FileChooser, Entry, ToggleButtonExt, SpinButtonExt, ComboBoxExt, FileChooserExt, EntryExt}; +use chrono::prelude::*; #[derive(Clone)] pub struct Ffmpeg { @@ -14,13 +18,17 @@ pub struct Ffmpeg { pub follow_mouse: CheckButton, pub record_frames: SpinButton, pub record_delay: SpinButton, - pub process_id: Option + pub process_id: Option, + pub saved_filename: Option, } impl Ffmpeg { - pub fn start_record(&mut self, x: i16, y: i16, width: i16, height: i16) -> u32 { + pub fn start_record(&mut self, x: u16, y: u16, width: u16, height: u16) -> u32 { if self.process_id.is_some() { - Command::new("kill").arg(format!("{}", self.process_id.unwrap())).output().unwrap(); + Command::new("kill") + .arg(format!("{}", self.process_id.unwrap())) + .output() + .unwrap(); } let mut ffmpeg_command: Command = Command::new("ffmpeg"); @@ -32,9 +40,11 @@ impl Ffmpeg { } // if show mouse switch is enabled, draw the mouse to video + ffmpeg_command.arg("-draw_mouse"); if self.record_mouse.get_active() { - ffmpeg_command.arg("-draw_mouse"); ffmpeg_command.arg("1"); + } else { + ffmpeg_command.arg("0"); } // if follow mouse switch is enabled, follow the mouse @@ -64,23 +74,30 @@ impl Ffmpeg { ffmpeg_command.arg("-strict"); ffmpeg_command.arg("-2"); } + ffmpeg_command.arg("-q"); ffmpeg_command.arg("1"); + self.saved_filename = Some( + self.filename + .0 + .get_filename() + .unwrap() + .join(PathBuf::from(format!( + "{}.{}", + if self.filename.1.get_text().to_string().trim().eq("") { + Utc::now().to_string() + } else { + self.filename.1.get_text().to_string().trim().to_string() + }, + self.filename.2.get_active_id().unwrap().to_string() + ))) + .as_path() + .display() + .to_string(), + ); - ffmpeg_command.arg({ - self.filename.0.get_filename() - .unwrap() - .join(PathBuf::from(format!( - "{}.{}", - if self.filename.1.get_text().to_string().trim().eq("") { - self.filename.1.get_text().to_string() - } else { - self.filename.1.get_text().to_string().trim().to_string() - }, - self.filename.2.get_active_id().unwrap().to_string() - ))) - }); + ffmpeg_command.arg(self.saved_filename.as_ref().unwrap()); ffmpeg_command.arg("-y"); // sleep for delay @@ -88,29 +105,23 @@ impl Ffmpeg { // start recording and return the process id self.process_id = Some(ffmpeg_command.spawn().unwrap().id()); - println!("{}", self.process_id.unwrap()); + println!("{}", self.process_id.unwrap()); self.process_id.unwrap() } pub fn stop_record(self) { if self.process_id.is_some() { - Command::new("kill").arg(format!("{}", self.process_id.unwrap())).output().unwrap(); + Command::new("kill") + .arg(format!("{}", self.process_id.unwrap())) + .output() + .unwrap(); } } pub fn play_record(self) { - Command::new("xdg-open").arg({ - self.filename.0.get_filename() - .unwrap() - .join(PathBuf::from(format!( - "{}.{}", - if self.filename.1.get_text().to_string().trim().eq("") { - self.filename.1.get_text().to_string() - } else { - self.filename.1.get_text().to_string().trim().to_string() - }, - self.filename.2.get_active_id().unwrap().to_string() - ))) - }).output().unwrap(); + if self.saved_filename.is_some() { + Command::new("xdg-open") + .arg(self.saved_filename.unwrap()).spawn().unwrap(); + } } } diff --git a/src/main.rs b/src/main.rs index e2b3ce7..0f2218e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate gio; extern crate gtk; mod config_management; mod ffmpeg_interface; +mod area_capture; // use gio::prelude::*; use std::rc::Rc; @@ -12,12 +13,15 @@ use gtk::prelude::*; use gtk::ComboBoxText; use gtk::{ AboutDialog, Builder, Button, CheckButton, CssProvider, Entry, FileChooser, Label, MenuItem, - SpinButton, Window, + SpinButton, Window }; use std::path::Path; use std::process::{Command, Stdio}; fn main() { + // use "GDK_BACKEND=x11" to make xwininfo work in Wayland by using XWayland + std::env::set_var("GDK_BACKEND", "x11"); + if gtk::init().is_err() { println!("Failed to initialize GTK."); return; @@ -40,6 +44,7 @@ fn main() { let play_button: Button = builder.get_object("playbutton").unwrap(); let window_grab_button: Button = builder.get_object("window_grab_button").unwrap(); let area_grab_button: Button = builder.get_object("area_grab_button").unwrap(); + let area_set_button: Button = builder.get_object("area_set_button").unwrap(); let frames_label: Label = builder.get_object("frames_label").unwrap(); let delay_label: Label = builder.get_object("delay_label").unwrap(); let command_label: Label = builder.get_object("command_label").unwrap(); @@ -210,11 +215,25 @@ fn main() { }); // Buttons - let _area_chooser_window = area_chooser_window.to_owned(); + let area_capture: Rc> = Rc::new(RefCell::new(area_capture::AreaCapture::new())); + let mut _area_capture = area_capture.clone(); + window_grab_button.connect_clicked(move |_| { + _area_capture.borrow_mut().get_area(); + }); + + let _area_chooser_window = area_chooser_window.clone(); + let mut _area_capture = area_capture.clone(); area_grab_button.connect_clicked(move |_| { _area_chooser_window.show(); }); + let _area_chooser_window = area_chooser_window.clone(); + let mut _area_capture = area_capture.clone(); + area_set_button.connect_clicked(move |_| { + _area_capture.borrow_mut().get_window_by_name("Area Chooser"); + _area_chooser_window.hide(); + }); + // init record struct let ffmpeg_record_interface: Rc> = Rc::new(RefCell::new(ffmpeg_interface::Ffmpeg { filename: (folder_chooser, filename_entry, format_chooser_combobox), @@ -225,13 +244,15 @@ fn main() { follow_mouse: follow_mouse_switch, record_frames: frames_spin, record_delay: delay_spin, - process_id: None + process_id: None, + saved_filename: None })); let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone(); - + let mut _area_capture = area_capture.clone(); record_button.connect_clicked(move |_| { - _ffmpeg_record_interface.borrow_mut().start_record(0, 0, 512, 512); + let _area_capture = _area_capture.borrow_mut().clone(); + _ffmpeg_record_interface.borrow_mut().start_record(_area_capture.x, _area_capture.y, _area_capture.width, _area_capture.height); }); let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone(); @@ -246,14 +267,17 @@ fn main() { // Windows // hide area chooser after it deleted. - let _area_chooser_window = area_chooser_window.to_owned(); + let _area_chooser_window = area_chooser_window.clone(); area_chooser_window.connect_delete_event(move |_, _event: &gdk::Event| { _area_chooser_window.hide(); Inhibit(true) }); // close the application when main window destroy - main_window.connect_destroy(|_| { + let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone(); + main_window.connect_destroy(move |_| { + // stop recording before close the application + _ffmpeg_record_interface.borrow_mut().clone().stop_record(); gtk::main_quit(); }); diff --git a/windows/ui.glade b/windows/ui.glade index 36acbd6..79b2ed5 100644 --- a/windows/ui.glade +++ b/windows/ui.glade @@ -67,13 +67,12 @@ False True - + True True True half True - True @@ -345,7 +344,6 @@ 5 5 5 - True