make window selection, area selection functional

This commit is contained in:
Salem Yaslem 2021-02-12 19:04:42 +03:00
parent 42b77654a4
commit 6d7a0f2a5b
6 changed files with 262 additions and 44 deletions

95
Cargo.lock generated
View File

@ -6,6 +6,15 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" 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]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.38" version = "1.0.38"
@ -38,6 +47,12 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.2.1" version = "1.2.1"
@ -48,10 +63,12 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
name = "blue-recorder" name = "blue-recorder"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono",
"gdk", "gdk",
"gio", "gio",
"glib", "glib",
"gtk", "gtk",
"regex",
"rust-ini", "rust-ini",
] ]
@ -99,6 +116,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "dlv-list" name = "dlv-list"
version = "0.2.2" version = "0.2.2"
@ -282,7 +312,7 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"wasi", "wasi 0.9.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -460,6 +490,25 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.5.2" version = "1.5.2"
@ -631,6 +680,24 @@ dependencies = [
"rand_core", "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]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.16.1" version = "0.16.1"
@ -717,6 +784,26 @@ dependencies = [
"syn", "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]] [[package]]
name = "toml" name = "toml"
version = "0.5.8" 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" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@ -10,6 +10,8 @@ edition = "2018"
gdk = "0.13.2" gdk = "0.13.2"
glib = "0.10.3" glib = "0.10.3"
rust-ini = "0.16" rust-ini = "0.16"
regex = "1.4.3"
chrono = "0.4.19"
[dependencies.gtk] [dependencies.gtk]
version = "0.9.0" version = "0.9.0"

90
src/area_capture.rs Normal file
View File

@ -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::<u16>()
.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::<u16>()
.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::<u16>()
.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::<u16>()
.unwrap();
(x, y, width, height)
}

View File

@ -1,8 +1,12 @@
use gtk::{
CheckButton, ComboBoxExt, ComboBoxText, Entry, EntryExt, FileChooser, FileChooserExt,
SpinButton, SpinButtonExt, ToggleButtonExt,
};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
use gtk::{CheckButton, SpinButton, ComboBoxText, FileChooser, Entry, ToggleButtonExt, SpinButtonExt, ComboBoxExt, FileChooserExt, EntryExt}; use chrono::prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct Ffmpeg { pub struct Ffmpeg {
@ -14,13 +18,17 @@ pub struct Ffmpeg {
pub follow_mouse: CheckButton, pub follow_mouse: CheckButton,
pub record_frames: SpinButton, pub record_frames: SpinButton,
pub record_delay: SpinButton, pub record_delay: SpinButton,
pub process_id: Option<u32> pub process_id: Option<u32>,
pub saved_filename: Option<String>,
} }
impl Ffmpeg { 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() { 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"); 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 // if show mouse switch is enabled, draw the mouse to video
ffmpeg_command.arg("-draw_mouse");
if self.record_mouse.get_active() { if self.record_mouse.get_active() {
ffmpeg_command.arg("-draw_mouse");
ffmpeg_command.arg("1"); ffmpeg_command.arg("1");
} else {
ffmpeg_command.arg("0");
} }
// if follow mouse switch is enabled, follow the mouse // if follow mouse switch is enabled, follow the mouse
@ -64,23 +74,30 @@ impl Ffmpeg {
ffmpeg_command.arg("-strict"); ffmpeg_command.arg("-strict");
ffmpeg_command.arg("-2"); ffmpeg_command.arg("-2");
} }
ffmpeg_command.arg("-q"); ffmpeg_command.arg("-q");
ffmpeg_command.arg("1"); 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({ ffmpeg_command.arg(self.saved_filename.as_ref().unwrap());
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("-y"); ffmpeg_command.arg("-y");
// sleep for delay // sleep for delay
@ -88,29 +105,23 @@ impl Ffmpeg {
// start recording and return the process id // start recording and return the process id
self.process_id = Some(ffmpeg_command.spawn().unwrap().id()); self.process_id = Some(ffmpeg_command.spawn().unwrap().id());
println!("{}", self.process_id.unwrap()); println!("{}", self.process_id.unwrap());
self.process_id.unwrap() self.process_id.unwrap()
} }
pub fn stop_record(self) { pub fn stop_record(self) {
if self.process_id.is_some() { 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) { pub fn play_record(self) {
Command::new("xdg-open").arg({ if self.saved_filename.is_some() {
self.filename.0.get_filename() Command::new("xdg-open")
.unwrap() .arg(self.saved_filename.unwrap()).spawn().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();
} }
} }

View File

@ -3,6 +3,7 @@ extern crate gio;
extern crate gtk; extern crate gtk;
mod config_management; mod config_management;
mod ffmpeg_interface; mod ffmpeg_interface;
mod area_capture;
// use gio::prelude::*; // use gio::prelude::*;
use std::rc::Rc; use std::rc::Rc;
@ -12,12 +13,15 @@ use gtk::prelude::*;
use gtk::ComboBoxText; use gtk::ComboBoxText;
use gtk::{ use gtk::{
AboutDialog, Builder, Button, CheckButton, CssProvider, Entry, FileChooser, Label, MenuItem, AboutDialog, Builder, Button, CheckButton, CssProvider, Entry, FileChooser, Label, MenuItem,
SpinButton, Window, SpinButton, Window
}; };
use std::path::Path; use std::path::Path;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
fn main() { 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() { if gtk::init().is_err() {
println!("Failed to initialize GTK."); println!("Failed to initialize GTK.");
return; return;
@ -40,6 +44,7 @@ fn main() {
let play_button: Button = builder.get_object("playbutton").unwrap(); let play_button: Button = builder.get_object("playbutton").unwrap();
let window_grab_button: Button = builder.get_object("window_grab_button").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_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 frames_label: Label = builder.get_object("frames_label").unwrap();
let delay_label: Label = builder.get_object("delay_label").unwrap(); let delay_label: Label = builder.get_object("delay_label").unwrap();
let command_label: Label = builder.get_object("command_label").unwrap(); let command_label: Label = builder.get_object("command_label").unwrap();
@ -210,11 +215,25 @@ fn main() {
}); });
// Buttons // Buttons
let _area_chooser_window = area_chooser_window.to_owned(); let area_capture: Rc<RefCell<area_capture::AreaCapture>> = 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_grab_button.connect_clicked(move |_| {
_area_chooser_window.show(); _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 // init record struct
let ffmpeg_record_interface: Rc<RefCell<ffmpeg_interface::Ffmpeg>> = Rc::new(RefCell::new(ffmpeg_interface::Ffmpeg { let ffmpeg_record_interface: Rc<RefCell<ffmpeg_interface::Ffmpeg>> = Rc::new(RefCell::new(ffmpeg_interface::Ffmpeg {
filename: (folder_chooser, filename_entry, format_chooser_combobox), filename: (folder_chooser, filename_entry, format_chooser_combobox),
@ -225,13 +244,15 @@ fn main() {
follow_mouse: follow_mouse_switch, follow_mouse: follow_mouse_switch,
record_frames: frames_spin, record_frames: frames_spin,
record_delay: delay_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 _ffmpeg_record_interface = ffmpeg_record_interface.clone();
let mut _area_capture = area_capture.clone();
record_button.connect_clicked(move |_| { 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(); let mut _ffmpeg_record_interface = ffmpeg_record_interface.clone();
@ -246,14 +267,17 @@ fn main() {
// Windows // Windows
// hide area chooser after it deleted. // 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.connect_delete_event(move |_, _event: &gdk::Event| {
_area_chooser_window.hide(); _area_chooser_window.hide();
Inhibit(true) Inhibit(true)
}); });
// close the application when main window destroy // 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(); gtk::main_quit();
}); });

View File

@ -67,13 +67,12 @@
<property name="has_subtitle">False</property> <property name="has_subtitle">False</property>
<property name="show_close_button">True</property> <property name="show_close_button">True</property>
<child> <child>
<object class="GtkButton" id="areasettings"> <object class="GtkButton" id="area_set_button">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="receives_default">True</property> <property name="receives_default">True</property>
<property name="relief">half</property> <property name="relief">half</property>
<property name="always_show_image">True</property> <property name="always_show_image">True</property>
<signal name="clicked" handler="areasettings" swapped="no"/>
<child> <child>
<object class="GtkGrid"> <object class="GtkGrid">
<property name="visible">True</property> <property name="visible">True</property>
@ -345,7 +344,6 @@
<property name="margin_right">5</property> <property name="margin_right">5</property>
<property name="margin_top">5</property> <property name="margin_top">5</property>
<property name="margin_bottom">5</property> <property name="margin_bottom">5</property>
<signal name="clicked" handler="selectarea" swapped="no"/>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>