diff --git a/Cargo.lock b/Cargo.lock index 130998e..9fde414 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" + [[package]] name = "atty" version = "0.2.14" @@ -68,6 +74,7 @@ dependencies = [ name = "easymacros" version = "0.2.0" dependencies = [ + "anyhow", "clap", "x11", ] diff --git a/Cargo.toml b/Cargo.toml index c65e9e7..be5aee0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.65" x11 = { version = "2.19.1", features = ["xlib", "xtest"] } -clap = { version = "3.2.4", features = ["derive"] } \ No newline at end of file +clap = { version = "3.2.4", features = ["derive"] } diff --git a/README.md b/README.md index 208c5a3..2c4eb65 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc - [ ] make modes listen for numbers/amounts of repetitions - [ ] make shortcuts be able to listen for other shortcuts and inputs - [ ] rofi integration + - [ ] autorun stuff on some windows? - [ ] Proper, safe xlib wrapper # diff --git a/src/bin/easymacroplay.rs b/src/bin/easymacroplay.rs index ba1960a..32c06cb 100644 --- a/src/bin/easymacroplay.rs +++ b/src/bin/easymacroplay.rs @@ -1,146 +1,173 @@ use clap::Parser; -use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay}; +use easymacros::chartbl::CHARTBL; +use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay}; use std::ffi::CString; -use std::process::{Command, exit}; +use std::io::stdin; +use std::process::{exit, Command}; use std::time::Duration; use std::{fs, thread}; -use std::io::stdin; use x11::keysym::XK_Shift_L; -use easymacros::chartbl::CHARTBL; /// 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, + /// 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 args = Args::parse(); - let display = get_remote(args.display); - let delay = args.delay.unwrap_or(10); + 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"); + 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(); + 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 { - 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); - } - } + loop { + 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(); + display.close(); } fn get_remote(display_name: Option) -> XDisplay { - let display = XDisplay::open(display_name); + let display = XDisplay::open(display_name); - if !display.has_xtest() { - eprintln!("XTest not supported!"); - display.close(); - exit(1) - } + if !display.has_xtest() { + eprintln!("XTest not supported!"); + display.close(); + exit(1) + } - display.grab_control(); - display.sync(); + display.grab_control(); + display.sync(); - display + display } fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) { - let instruction_split: Vec<&str> = instruction.split(' ').collect(); + 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), + 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), - } + "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); + // 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; - } + 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 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); + 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; - } + // 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); } -} \ No newline at end of file + 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 index 9d91d88..9107599 100644 --- a/src/bin/easymacrorec.rs +++ b/src/bin/easymacrorec.rs @@ -1,189 +1,198 @@ extern crate core; use std::ffi::c_void; -use std::os::raw::{c_char}; -use std::process::{exit}; +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 x11::xlib::{ + CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, + XKeyEvent, +}; +use x11::xrecord::{ + XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData, +}; -use easymacros::{BUTTONPRESS_U8, BUTTONRELEASE_U8, Instructions, KEYPRESS_U8, KEYRELEASE_U8, MOTIONNOTIFY_U8, Position}; 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, + /// 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 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); + 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, - ); + event_loop(display, recorded_display, stop_key, writer, args.max_delay); - display.close(); + display.close(); } fn get_stop_key(display: XDisplay) -> Keycode { - let screen = display.get_default_screen(); + let screen = display.get_default_screen(); - let root = display.get_root_window(screen); - let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime); + 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); - } + if potential_err != GrabSuccess { + eprintln!("Couldn't grab keyboard!"); + exit(1); + } - println!("Press the key you want to use to stop recording the macro."); + 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; - } + 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); + display.ungrab_keyboard(CurrentTime); + display.ungrab_pointer(CurrentTime); - stop_key + stop_key } fn event_loop( - xdpy: XDisplay, - recdpy: XDisplay, - stop_key: Keycode, - mut writer: MacroWriter, - max_delay: Option