diff --git a/src/bin/easymacrorec.rs b/src/bin/easymacrorec.rs index e5acdbb..a435101 100644 --- a/src/bin/easymacrorec.rs +++ b/src/bin/easymacrorec.rs @@ -3,8 +3,11 @@ extern crate core; use std::os::raw::{c_char, c_uchar, c_uint}; use std::process::{exit, ExitCode}; use std::ptr::{addr_of, slice_from_raw_parts}; -use std::{slice, thread}; +use std::{io, slice, thread}; +use std::io::Write; use std::ffi::c_void; +use std::fmt::{format}; +use std::fs::{File, OpenOptions}; use clap::Parser; use x11::keysym::XK_Escape; use x11::xinput2::XIGrabModeSync; @@ -16,7 +19,7 @@ use easymacros::x11_safe_wrapper::{Keycode, XDisplay}; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Args { - /// The file that contains the macro to run. + /// 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. @@ -34,7 +37,13 @@ fn main() { let screen = display.get_default_screen(); dbg!(stop_key); - ev_loop(display, recorded_display, screen, stop_key); + let mut outfile: Box = if let Some(outfile) = args.output_file { + Box::new(File::create(outfile).expect("Failed to create output file")) + } else { + Box::new(io::stdout()) + }; + + ev_loop(display, recorded_display, screen, stop_key, outfile); display.close(); } @@ -68,21 +77,37 @@ fn get_stop_key(display: XDisplay) -> Keycode { } } + display.ungrab_keyboard(CurrentTime); + display.ungrab_pointer(CurrentTime); + stop_key } -fn ev_loop(display: XDisplay, recordeddpy: XDisplay, screen: i32, stop_key: Keycode) { +fn ev_loop(display: XDisplay, recordeddpy: XDisplay, screen: i32, stop_key: Keycode, mut outfile: Box) { let root = display.get_root_window(screen); let protocol_ranges = unsafe { XRecordAllocRange() }; + let pointer_pos = display.query_pointer_pos(); + + if pointer_pos != (-1, -1) { + writeln!(&mut outfile, "MotionNotify {} {}", pointer_pos.0, pointer_pos.1) + .expect("Failed to write to outfile"); + } + let ctx = recordeddpy.create_record_context(protocol_ranges); let ev_cb_data = EvCallbackData { + outfile, xdpy: display, recdpy: recordeddpy, ctx, stop_key, - nr_evs: 0, - working: true + ev_nr: 0, + working: true, + x: pointer_pos.0 as i16, + y: pointer_pos.1 as i16, + no_keypress_yet: true, + last_event: 0, + moving: false, }; if !recordeddpy.enable_context_async(ctx, Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char) { @@ -97,66 +122,124 @@ fn ev_loop(display: XDisplay, recordeddpy: XDisplay, screen: i32, stop_key: Keyc unsafe { XFree(protocol_ranges as *mut c_void) }; } -#[derive(Debug)] #[repr(C)] pub struct EvCallbackData { - pub xdpy: XDisplay, - pub recdpy: XDisplay, - pub stop_key: Keycode, - pub nr_evs: u32, - pub working: bool, - pub ctx: XRecordContext, - // x: i32, - // y: i32, + outfile: Box, + xdpy: XDisplay, + recdpy: XDisplay, + stop_key: Keycode, + ev_nr: u32, + working: bool, + ctx: XRecordContext, + x: i16, + y: i16, + no_keypress_yet: bool, + last_event: u64, + moving: bool, } unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRecordInterceptData) { - dbg!(intercept_data); - dbg!(closure); let data = &mut *(closure as *mut EvCallbackData); let intercept_data = &mut *intercept_data; - dbg!(&intercept_data); - dbg!(&data); if intercept_data.category == XRecordStartOfData { println!("Got start of data!"); + XRecordFreeData(intercept_data); return; } else if intercept_data.category == XRecordEndOfData { println!("Got end of data!"); + XRecordFreeData(intercept_data); return; } - data.nr_evs += 1; - // println!("nr: {:?}, len: {:?}", data, intercept_data.data_len); - dbg!(intercept_data.data); let ev_type = *(intercept_data.data as *const u8); - match ev_type { - KEYPRESS_U8 => { - let kc = *((intercept_data.data as usize + 1) as *const u8); - let stop = kc == data.stop_key as u8; - if stop { - println!("stop key detected!"); - data.working = false; - XRecordFreeData(intercept_data) - } else { - // let keyname = data.xdpy.keycode_to_string(kc as u32); - let keyname = data.xdpy.keycode_to_string(44); - let rstring = format!("KeyPress {}", &keyname.to_str().unwrap()); - // let rstring = format!("KeyPress {}", kc); - // dbg!(kc); - dbg!(&rstring); - std::mem::forget(keyname); - // XRecordFreeData(intercept_data) - } + if data.x == -1 || data.y == -1 { + if ev_type == MOTIONNOTIFY_U8 { + data.x = *((intercept_data.data as usize + std::mem::size_of::() * 10) as *const i16); + data.y = *((intercept_data.data as usize + std::mem::size_of::() * 11) as *const i16); + println!("MotionNotify {} {}", data.x, data.y); + } else { + println!("Move your cursor so the macro can start with a fixed cursor position!"); + println!("Skipping event..."); + } + } else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 { + println!("Skipping KeyRelease without recorded KeyPress..."); + } else { + // if + match ev_type { + MOTIONNOTIFY_U8 => { + let root_x = *((intercept_data.data as usize + std::mem::size_of::() * 10) as *const i16); + let root_y = *((intercept_data.data as usize + std::mem::size_of::() * 11) as *const i16); + data.x = root_x; + data.y = root_y; + + if !data.moving { + data.moving = true; + } + } + KEYPRESS_U8 | KEYRELEASE_U8 => { + let kc: u8 = *((intercept_data.data as usize + 1) as *const u8); + let keyname = data.xdpy.keycode_to_string(kc as u32); + + if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 { + // println!("stop key detected, recording done!"); + data.working = false; + } else { + if ev_type == KEYPRESS_U8 { + data.no_keypress_yet = false; + } + + if (intercept_data.server_time - data.last_event) != 0 { + println!("test"); + writeln!(&mut data.outfile,"Delay {}", intercept_data.server_time - data.last_event) + .expect("Failed to write to outfile."); + data.last_event = intercept_data.server_time; + } + + if data.moving { + writeln!(&mut data.outfile, "MotionNotify {} {}", data.x, data.y) + .expect("Failed to write to outfile."); + data.moving = false; + } + + writeln!( + &mut data.outfile, + "{} {}", + if ev_type == KEYPRESS_U8 { "KeyStrPress" } else { "KeyRelease" }, + keyname.to_str().unwrap() + ).expect("Failed to write to outfile."); + } + } + BUTTONPRESS_U8 | BUTTONRELEASE_U8 => { + let bc: u8 = *((intercept_data.data as usize + 1) as *const u8); + + if (intercept_data.server_time - data.last_event) != 0 { + println!("testb"); + writeln!(&mut data.outfile,"Delay {}", intercept_data.server_time - data.last_event) + .expect("Failed to write to outfile."); + data.last_event = intercept_data.server_time; + } + + if data.moving { + writeln!(&mut data.outfile, "MotionNotify {} {}", data.x, data.y) + .expect("Failed to write to outfile."); + data.moving = false; + } + + writeln!( + &mut data.outfile, + "{} {}", + if ev_type == BUTTONPRESS_U8 { "ButtonPress" } else { "ButtonRelease" }, + bc + ).expect("Failed to write to outfile."); + } + _ => eprintln!("Unknown event type: {:?}", ev_type) } - KEYRELEASE_U8 => {} - BUTTONPRESS_U8 => {} - BUTTONRELEASE_U8 => {} - MOTIONNOTIFY_U8 => {} - _ => eprintln!("Unknown event type: {:?}", ev_type) } + data.ev_nr += 1; + XRecordFreeData(intercept_data) } const KEYPRESS_U8: u8 = KeyPress as u8; diff --git a/src/x11_safe_wrapper.rs b/src/x11_safe_wrapper.rs index d6f57fe..376e279 100644 --- a/src/x11_safe_wrapper.rs +++ b/src/x11_safe_wrapper.rs @@ -1,8 +1,8 @@ use std::cell::{Ref, RefCell}; use std::env; -use std::ffi::CString; +use std::ffi::{CStr, CString}; use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulong}; -use x11::xlib::{Display, GenericEvent, KeyCode, KeyPress, MotionNotify, Status, Time, Window, XAllowEvents, XAnyEvent, XCloseDisplay, XDefaultScreen, XEvent, XFlush, XGrabKeyboard, XKeycodeToKeysym, XKeyEvent, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent}; +use x11::xlib::{Display, GenericEvent, KeyCode, KeyPress, MotionNotify, Status, Time, Window, XAllowEvents, XAnyEvent, XCloseDisplay, XDefaultScreen, XEvent, XFlush, XGrabKeyboard, XID, XKeycodeToKeysym, XKeyEvent, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XQueryPointer, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent}; use x11::xrecord::{XRecordAllClients, XRecordAllocRange, XRecordClientInfo, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange}; use x11::xtest::{ XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl, @@ -49,6 +49,26 @@ impl XDisplay { } } + pub fn query_pointer_pos(&self) -> (i32, i32) { + let mut r: (i32, i32) = (-1, -1); + let mut unneeded_ints: (i32, i32) = (0, 0); + let mut unneeded_wins: (u64, u64) = (0, 0); + let mut unneeded_mask: u32 = 0; + unsafe { XQueryPointer( + self.ptr, + self.get_root_window(self.get_default_screen()), + &mut unneeded_wins.0, + &mut unneeded_wins.1, + &mut r.0, + &mut r.1, + &mut unneeded_ints.0, + &mut unneeded_ints.1, + &mut unneeded_mask + ) }; + + r + } + pub fn get_default_screen(&self) -> c_int { unsafe { XDefaultScreen(self.ptr) } } @@ -232,11 +252,7 @@ pub fn string_to_keysym(string: &[u8]) -> Keysym { } pub fn keysym_to_string(keysym: Keysym) -> CString { unsafe { - let raw = XKeysymToString(keysym); - dbg!(raw); - let r = CString::from_raw(raw); - r + let cstr = CStr::from_ptr(XKeysymToString(keysym)); + CString::from(cstr) } - // .into_string() - // .expect("failed to convert CString into Rust String") }