diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..e69de29 diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..a41b4d2 --- /dev/null +++ b/shell.nix @@ -0,0 +1,13 @@ +with import {}; +pkgs.mkShell { + buildInputs = with pkgs; [ + cargo + rustc + rust-analyzer + rustfmt + pkg-config + xorg.libX11 + xorg.libXtst + ]; + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; +} diff --git a/src/bin/easymacrorec.rs b/src/bin/easymacrorec.rs index 4227dc4..df8d6bb 100644 --- a/src/bin/easymacrorec.rs +++ b/src/bin/easymacrorec.rs @@ -1,10 +1,12 @@ use std::os::raw::{c_char, c_uint}; use std::process::{exit, ExitCode}; +use std::ptr::addr_of; +use std::thread; use clap::Parser; use x11::keysym::XK_Escape; use x11::xinput2::XIGrabModeSync; use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyCode, KeyPress, KeyPressMask, SyncPointer, XEvent, XPointer}; -use x11::xrecord::{XRecordEndOfData, XRecordInterceptData, XRecordStartOfData}; +use x11::xrecord::{XRecordCreateContext, XRecordEndOfData, XRecordInterceptData, XRecordStartOfData}; use easymacros::x11_safe_wrapper::{Keycode, XDisplay}; /// Macro recording module for easymacros. Outputs are partially compatible with xmacro. @@ -23,12 +25,15 @@ fn main() { let display = XDisplay::open(args.display); - let stop_key = get_stop_key(display); + let stop_key = get_stop_key(&display); + let screen = display.get_default_screen(); dbg!(stop_key); + + ev_loop(display, screen, stop_key); } -fn get_stop_key(display: XDisplay) -> Keycode { +fn get_stop_key(display: &XDisplay) -> Keycode { let screen = display.get_default_screen(); let root = display.get_root_window(screen); @@ -63,20 +68,38 @@ fn get_stop_key(display: XDisplay) -> Keycode { fn ev_loop(display: XDisplay, screen: i32, stop_key: Keycode) { let root = display.get_root_window(screen); + + let mut ev_cb_data = EvCallbackData { stop_key, nr_evs: 0, working: true}; + display.create_record_context(); + display.enable_context_async(Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char); + + while ev_cb_data.working { + display.process_replies(); + thread::sleep(std::time::Duration::from_millis(100)) + } } -struct EvCallbackData { - stop_key: Keycode, - x: i32, - y: i32, +#[repr(C)] +pub struct EvCallbackData { + pub stop_key: Keycode, + pub nr_evs: u32, + pub working: bool, + // x: i32, + // y: i32, } + unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRecordInterceptData) { + println!("Got event!!!"); + let data = &mut *(closure as *mut EvCallbackData); let intercept_data = &mut *intercept_data; if intercept_data.category == XRecordStartOfData { println!("Got start of data!"); } else if intercept_data.category == XRecordEndOfData { println!("Got end of data!");} - - + data.nr_evs += 1; + print!("nr: {}", data.nr_evs); + if data.nr_evs >= 10 { + data.working = false; + } } \ No newline at end of file diff --git a/src/x11_safe_wrapper.rs b/src/x11_safe_wrapper.rs index 8433d86..17e401b 100644 --- a/src/x11_safe_wrapper.rs +++ b/src/x11_safe_wrapper.rs @@ -1,15 +1,17 @@ +use std::cell::RefCell; use std::env; use std::ffi::CString; -use std::os::raw::{c_char, c_int, c_uint, c_ulong}; -use x11::xlib::{Display, GenericEvent, Time, Window, XAllowEvents, XAnyEvent, XCloseDisplay, XDefaultScreen, XEvent, XFlush, XGrabKeyboard, XKeyEvent, XKeysymToKeycode, XOpenDisplay, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent}; -use x11::xrecord::XRecordQueryVersion; +use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulong}; +use x11::xlib::{Display, GenericEvent, KeyPress, MotionNotify, Time, Window, XAllowEvents, XAnyEvent, XCloseDisplay, XDefaultScreen, XEvent, XFlush, XGrabKeyboard, XKeyEvent, XKeysymToKeycode, XOpenDisplay, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent}; +use x11::xrecord::{XRecordAllClients, XRecordAllocRange, XRecordClientInfo, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordEnableContextAsync, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion}; use x11::xtest::{ - XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl, - XTestQueryExtension, + XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl, + XTestQueryExtension, }; pub struct XDisplay { - ptr: *mut Display, + ptr: *mut Display, + xrecordctx: RefCell>, } pub type Keysym = c_ulong; @@ -19,146 +21,192 @@ const FALSE_C: c_int = 0; const TRUE_C: c_int = 1; impl XDisplay { - pub fn open(display_name: Option) -> Self { - let name = CString::new(if let Some(name) = display_name { - name - } else { - env::var("DISPLAY").expect("DISPLAY is not set") - }) - .unwrap(); - let name_ptr = name.as_bytes().as_ptr(); - let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) }; + pub fn open(display_name: Option) -> Self { + let name = CString::new(if let Some(name) = display_name { + name + } else { + env::var("DISPLAY").expect("DISPLAY is not set") + }).unwrap(); + let name_ptr = name.as_bytes().as_ptr(); + let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) }; - Self { ptr: display_ptr } - } - - pub fn close(self) { - unsafe { XCloseDisplay(self.ptr) }; - } - - pub fn sync(&self) { - unsafe { - XSync(self.ptr, c_int::from(false)); + Self { + ptr: display_ptr, + xrecordctx: RefCell::new(None), } - } + } - pub fn flush(&self) { - unsafe { - XFlush(self.ptr); - } - } + pub fn close(self) { + unsafe { XCloseDisplay(self.ptr) }; + } - pub fn get_default_screen(&self) -> c_int { - unsafe { XDefaultScreen(self.ptr) } - } + pub fn sync(&self) { + unsafe { + XSync(self.ptr, c_int::from(false)); + } + } - pub fn get_root_window(&self, screen_nr: c_int) -> Window { - unsafe { XRootWindow(self.ptr, screen_nr) } - } + pub fn flush(&self) { + unsafe { + XFlush(self.ptr); + } + } - pub fn keysym_to_keycode(&self, keysym: c_ulong) -> Keycode { - unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode } - } + pub fn get_default_screen(&self) -> c_int { + unsafe { XDefaultScreen(self.ptr) } + } - pub fn string_to_keycode(&self, string: &[u8]) -> Keycode { - self.keysym_to_keycode(string_to_keysym(string)) - } + pub fn get_root_window(&self, screen_nr: c_int) -> Window { + unsafe { XRootWindow(self.ptr, screen_nr) } + } - // XTest stuff - pub fn has_xtest(&self) -> bool { - let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0); - let has_extension = unsafe { - XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3) + pub fn keysym_to_keycode(&self, keysym: c_ulong) -> Keycode { + unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode } + } + + pub fn string_to_keycode(&self, string: &[u8]) -> Keycode { + self.keysym_to_keycode(string_to_keysym(string)) + } + + // XTest stuff + pub fn has_xtest(&self) -> bool { + let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0); + let has_extension = unsafe { + XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3) + }; + has_extension != 0 + } + + pub fn send_fake_keypress_from_string(&self, string: &[u8]) { + self.send_fake_keypress_from_keysym(string_to_keysym(string)) + } + + pub fn send_fake_keypress_from_keysym(&self, ks: Keysym) { + self.send_fake_keypress_from_code(self.keysym_to_keycode(ks)) + } + + pub fn send_fake_keypress_from_code(&self, code: Keycode) { + unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, 10) }; + self.flush(); + } + + pub fn send_fake_buttonpress(&self, button: u32) { + unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, 10) }; + } + + pub fn send_fake_buttonrelease(&self, button: u32) { + unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, 10) }; + } + + pub fn send_fake_keyrelease_from_string(&self, string: &[u8]) { + self.send_fake_keyrelease_from_keysym(string_to_keysym(string)) + } + + pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym) { + self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks)) + } + + pub fn send_fake_keyrelease_from_code(&self, code: Keycode) { + unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, 10) }; + self.flush(); + } + + pub fn send_fake_motion_event(&self, x: i32, y: i32) { + unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, 10) }; + self.flush(); + } + + pub fn grab_control(&self) { + unsafe { + XTestGrabControl(self.ptr, TRUE_C); + } + } + + pub fn allow_events(&self, event_mode: i32, time: Time) { + unsafe { XAllowEvents(self.ptr, event_mode, time) }; + } + + pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 { + unsafe { + XGrabKeyboard( + self.ptr, + window, + c_int::from(owner_events), + pointer_mode, + keyboard_mode, + time, + ) + } + } + + pub fn ungrab_keyboard(&self, time: Time) { + unsafe { XUngrabKeyboard(self.ptr, time) }; + } + + pub fn ungrab_pointer(&self, time: Time) { + unsafe { XUngrabPointer(self.ptr, time) }; + } + pub fn window_event(&self, window: Window, event_mask: i64) -> XEvent { + // maybe dirty hack to initialize the event var?? idk how else to do this + let mut r: XEvent = XEvent { type_: GenericEvent }; + + unsafe { XWindowEvent(self.ptr, window, event_mask, &mut r); } + + r + } + + // XRecord stuff + pub fn has_xrecord(&self) -> bool { + let mut xrecord_version: (c_int, c_int) = (0, 0); + let xrec_res = unsafe { XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1) }; + xrec_res == 0 + } + + pub fn create_record_context(&self) { + if self.xrecordctx.borrow().is_some() { + panic!("Tried to create xrecord context with one already present"); + } + + let mut protocol_ranges = unsafe { XRecordAllocRange() }; + unsafe { + (*protocol_ranges).device_events.first = KeyPress as c_uchar; + (*protocol_ranges).device_events.last = MotionNotify as c_uchar; + } + let mut clients: XRecordClientSpec = XRecordAllClients; + + let ctx: XRecordContext = unsafe { + XRecordCreateContext( + self.ptr, + 0, + &mut clients, + 1, + &mut protocol_ranges, + 1 + ) }; - has_extension != 0 - } + self.xrecordctx.replace(Some(ctx)); + } - pub fn send_fake_keypress_from_string(&self, string: &[u8]) { - self.send_fake_keypress_from_keysym(string_to_keysym(string)) - } - - pub fn send_fake_keypress_from_keysym(&self, ks: Keysym) { - self.send_fake_keypress_from_code(self.keysym_to_keycode(ks)) - } - - pub fn send_fake_keypress_from_code(&self, code: Keycode) { - unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, 10) }; - self.flush(); - } - - pub fn send_fake_buttonpress(&self, button: u32) { - unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, 10) }; - } - - pub fn send_fake_buttonrelease(&self, button: u32) { - unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, 10) }; - } - - pub fn send_fake_keyrelease_from_string(&self, string: &[u8]) { - self.send_fake_keyrelease_from_keysym(string_to_keysym(string)) - } - - pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym) { - self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks)) - } - - pub fn send_fake_keyrelease_from_code(&self, code: Keycode) { - unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, 10) }; - self.flush(); - } - - pub fn send_fake_motion_event(&self, x: i32, y: i32) { - unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, 10) }; - self.flush(); - } - - pub fn grab_control(&self) { + pub fn enable_context_async(&self, + cb: Option, + closure: *mut c_char, + ) { unsafe { - XTestGrabControl(self.ptr, TRUE_C); - } + XRecordEnableContextAsync( + self.ptr, + self.xrecordctx.borrow_mut().unwrap(), + cb, + closure as *mut c_char + ) + }; } - pub fn allow_events(&self, event_mode: i32, time: Time) { - unsafe { XAllowEvents(self.ptr, event_mode, time) }; + pub fn process_replies(&self) { + unsafe { XRecordProcessReplies(self.ptr) }; } - - pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 { - unsafe { XGrabKeyboard( - self.ptr, - window, - c_int::from(owner_events), - pointer_mode, - keyboard_mode, - time - )} - } - - pub fn ungrab_keyboard(&self, time: Time) { - unsafe { XUngrabKeyboard(self.ptr, time) }; - } - - pub fn ungrab_pointer(&self, time: Time) { - unsafe { XUngrabPointer(self.ptr, time) }; - } - pub fn window_event(&self, window: Window, event_mask: i64) -> XEvent { - // maybe dirty hack to initialize the event var?? idk how else to do this - let mut r: XEvent = XEvent { type_: GenericEvent }; - - unsafe { XWindowEvent(self.ptr, window, event_mask, &mut r); } - - r - } - - // XRecord stuff - pub fn has_xrecord(&self) -> bool { - let mut xrecord_version: (c_int, c_int) = (0, 0); - let xrec_res = unsafe { XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1) }; - xrec_res == 0 - } - } /// Wrapper for XStringToKeysym. Remember to give a null terminated string! pub fn string_to_keysym(string: &[u8]) -> Keysym { - unsafe { XStringToKeysym(string.as_ptr() as *const c_char) } + unsafe { XStringToKeysym(string.as_ptr() as *const c_char) } }