diff --git a/Cargo.lock b/Cargo.lock index 9fde414..502a99d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ version = "0.2.0" dependencies = [ "anyhow", "clap", - "x11", + "xcb", ] [[package]] @@ -116,6 +116,12 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "once_cell" version = "1.12.0" @@ -128,12 +134,6 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -167,6 +167,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.18" @@ -252,11 +261,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "x11" -version = "2.19.1" +name = "xcb" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a" +checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9" dependencies = [ + "bitflags", "libc", - "pkg-config", + "quick-xml", ] diff --git a/Cargo.toml b/Cargo.toml index be5aee0..8d0f48d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" [dependencies] anyhow = "1.0.65" -x11 = { version = "2.19.1", features = ["xlib", "xtest"] } +xcb = { version = "1.1.1", features = [ "xtest", "record" ]} clap = { version = "3.2.4", features = ["derive"] } diff --git a/src/bin/easymacroplay.rs b/src/bin/easymacroplay.rs deleted file mode 100644 index 386c2cc..0000000 --- a/src/bin/easymacroplay.rs +++ /dev/null @@ -1,174 +0,0 @@ -use clap::Parser; -use easymacros::chartbl::CHARTBL; -use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay}; -use std::ffi::CString; -use std::io::stdin; -use std::process::{exit, Command}; -use std::time::Duration; -use std::{fs, thread}; -use x11::keysym::XK_Shift_L; - -/// Macro player module for easymacros. It's compatible with xmacro macros. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// The file that contains the macro to run. - #[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)] - input_file: Option, - /// Display to run the macro on. This uses the $DISPLAY environment variable by default. - #[clap(short = 'D', long)] - display: Option, - /// Delay for events to be sent. - #[clap(short, long)] - delay: Option, -} - -fn main() { - let args = Args::parse(); - - let display = get_remote(args.display); - let delay = args.delay.unwrap_or(10); - - if let Some(input_file_path) = args.input_file { - let input_file_contents = - fs::read_to_string(input_file_path).expect("Couldn't read macro file"); - - for instruction in input_file_contents.lines() { - run_instruction(instruction, &display, delay); - } - } else { - println!("No input file specified, reading from stdin."); - let stdin = stdin(); - - loop { - // TODO: Unify with macro_writer using trait objects - let mut line = String::new(); - stdin - .read_line(&mut line) - .expect("Couldn't read line from stdin"); - // Without this it crashes because apparently it doesn't properly read the next input line? - println!(); - line = line.trim().to_string(); - run_instruction(&*line, &display, delay); - } - } - - display.close(); -} - -fn get_remote(display_name: Option) -> XDisplay { - let display = XDisplay::open(display_name); - - if !display.has_xtest() { - eprintln!("XTest not supported!"); - display.close(); - exit(1) - } - - display.grab_control(); - display.sync(); - - display -} - -fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) { - let instruction_split: Vec<&str> = instruction.split(' ').collect(); - - match instruction_split[0] { - "Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())), - "ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay), - "ButtonRelease" => { - dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay) - } - "MotionNotify" => dpy.send_fake_motion_event( - instruction_split[1].parse().unwrap(), - instruction_split[2].parse().unwrap(), - delay, - ), - "KeyCodePress" => { - dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay) - } - "KeyCodeRelease" => { - dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay) - } - "KeySymPress" => { - dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay) - } - "KeySymRelease" => { - dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay) - } - - "KeySym" => { - let key: Keysym = instruction_split[1].parse().unwrap(); - dpy.send_fake_keypress_from_keysym(key, delay); - dpy.send_fake_keyrelease_from_keysym(key, delay); - } - "KeyStrPress" => dpy.send_fake_keypress_from_string( - CString::new(instruction_split[1]).unwrap().as_bytes(), - delay, - ), - "KeyStrRelease" => dpy.send_fake_keyrelease_from_string( - CString::new(instruction_split[1]).unwrap().as_bytes(), - delay, - ), - "KeyStr" => { - let keystring = CString::new(instruction_split[1]).unwrap(); - dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay); - dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay); - } - "String" => { - for c in instruction["String".len() + 1..].chars() { - send_char(dpy, c, delay); - } - } - "ExecBlock" | "ExecNoBlock" => { - let mut command = Command::new(instruction_split[1]); - for arg in &instruction_split[2..] { - command.arg(arg); - } - if instruction_split[0] == "ExecBlock" { - command.status().unwrap(); - } else { - command.spawn().unwrap(); - } - } - c => panic!("Unknown command {:?}", instruction_split), - } -} - -fn send_char(dpy: &XDisplay, c: char, delay: u64) { - // get keystring from character and turn it into a keysym - let keysym = string_to_keysym(CHARTBL[c as usize].as_ref()); - let keycode = dpy.keysym_to_keycode(keysym); - - if keycode == 0 { - eprintln!("No keycode found for character '{}'", c); - return; - } - - let map_ks = dpy.get_keyboard_mapping(keycode, 1); - if map_ks[0] == 0 { - eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode); - return; - } - - let (ks_lower, ks_upper) = dpy.convert_case(keysym); - - // check if shift has to be pressed as well - let mut shift_needed = true; - if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) { - shift_needed = false; - } - if keysym == ks_lower && keysym != ks_upper { - shift_needed = false; - } - - if shift_needed { - dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); - } - dpy.send_fake_keypress_from_code(keycode, delay); - dpy.send_fake_keyrelease_from_code(keycode, delay); - if shift_needed { - dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); - } -} diff --git a/src/bin/easymacrorec.rs b/src/bin/easymacrorec.rs deleted file mode 100644 index 9107599..0000000 --- a/src/bin/easymacrorec.rs +++ /dev/null @@ -1,198 +0,0 @@ -extern crate core; - -use std::ffi::c_void; -use std::os::raw::c_char; -use std::process::exit; -use std::ptr::addr_of; - -use clap::Parser; -use x11::xlib::{ - CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, - XKeyEvent, -}; -use x11::xrecord::{ - XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData, -}; - -use easymacros::ev_callback_data::EvCallbackData; -use easymacros::macro_writer::MacroWriter; -use easymacros::x11_safe_wrapper::{Keycode, XDisplay}; -use easymacros::{ - Instructions, Position, BUTTONPRESS_U8, BUTTONRELEASE_U8, KEYPRESS_U8, KEYRELEASE_U8, - MOTIONNOTIFY_U8, -}; - -/// Macro recording module for easymacros. Outputs are partially compatible with xmacro. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// The file to record the macro to. Defaults to writing to stdout. - #[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)] - output_file: Option, - /// Display to run the macro on. This uses the $DISPLAY environment variable by default. - #[clap(short = 'D', long)] - display: Option, - /// Max Delay in milliseconds for macro delays - #[clap(short, long)] - max_delay: Option, - /// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay. - #[clap(short, long)] - ignore_delay_capturing: bool, -} - -fn main() { - let args = Args::parse(); - - let display = XDisplay::open(args.display.clone()); - let recorded_display = XDisplay::open(args.display.clone()); - let stop_key = get_stop_key(display); - let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing); - - event_loop(display, recorded_display, stop_key, writer, args.max_delay); - - display.close(); -} - -fn get_stop_key(display: XDisplay) -> Keycode { - let screen = display.get_default_screen(); - - let root = display.get_root_window(screen); - let potential_err = - display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime); - - if potential_err != GrabSuccess { - eprintln!("Couldn't grab keyboard!"); - exit(1); - } - - println!("Press the key you want to use to stop recording the macro."); - - let stop_key; - loop { - display.allow_events(SyncPointer, CurrentTime); - let ev = XKeyEvent::from(display.window_event(root, KeyPressMask)); - stop_key = ev.keycode; - break; - } - - display.ungrab_keyboard(CurrentTime); - display.ungrab_pointer(CurrentTime); - - stop_key -} - -fn event_loop( - xdpy: XDisplay, - recdpy: XDisplay, - stop_key: Keycode, - mut writer: MacroWriter, - max_delay: Option