holy shitttt do NOT run this version it WILL break your desktop PLEASE DO NOT RUN
This commit is contained in:
parent
0b1cbcea0e
commit
39916cd6db
4 changed files with 223 additions and 139 deletions
0
.envrc
Normal file
0
.envrc
Normal file
13
shell.nix
Normal file
13
shell.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
with import <nixpkgs> {};
|
||||||
|
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}";
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
use std::os::raw::{c_char, c_uint};
|
use std::os::raw::{c_char, c_uint};
|
||||||
use std::process::{exit, ExitCode};
|
use std::process::{exit, ExitCode};
|
||||||
|
use std::ptr::addr_of;
|
||||||
|
use std::thread;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use x11::keysym::XK_Escape;
|
use x11::keysym::XK_Escape;
|
||||||
use x11::xinput2::XIGrabModeSync;
|
use x11::xinput2::XIGrabModeSync;
|
||||||
use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyCode, KeyPress, KeyPressMask, SyncPointer, XEvent, XPointer};
|
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};
|
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
|
||||||
|
|
||||||
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
|
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
|
||||||
|
@ -23,12 +25,15 @@ fn main() {
|
||||||
|
|
||||||
let display = XDisplay::open(args.display);
|
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);
|
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 screen = display.get_default_screen();
|
||||||
|
|
||||||
let root = display.get_root_window(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) {
|
fn ev_loop(display: XDisplay, screen: i32, stop_key: Keycode) {
|
||||||
let root = display.get_root_window(screen);
|
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 {
|
#[repr(C)]
|
||||||
stop_key: Keycode,
|
pub struct EvCallbackData {
|
||||||
x: i32,
|
pub stop_key: Keycode,
|
||||||
y: i32,
|
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) {
|
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 data = &mut *(closure as *mut EvCallbackData);
|
||||||
let intercept_data = &mut *intercept_data;
|
let intercept_data = &mut *intercept_data;
|
||||||
|
|
||||||
if intercept_data.category == XRecordStartOfData { println!("Got start of data!"); }
|
if intercept_data.category == XRecordStartOfData { println!("Got start of data!"); }
|
||||||
else if intercept_data.category == XRecordEndOfData { println!("Got end 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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,17 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::os::raw::{c_char, c_int, c_uint, c_ulong};
|
use std::os::raw::{c_char, c_int, c_uchar, 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::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::XRecordQueryVersion;
|
use x11::xrecord::{XRecordAllClients, XRecordAllocRange, XRecordClientInfo, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordEnableContextAsync, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion};
|
||||||
use x11::xtest::{
|
use x11::xtest::{
|
||||||
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
|
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
|
||||||
XTestQueryExtension,
|
XTestQueryExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct XDisplay {
|
pub struct XDisplay {
|
||||||
ptr: *mut Display,
|
ptr: *mut Display,
|
||||||
|
xrecordctx: RefCell<Option<XRecordContext>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Keysym = c_ulong;
|
pub type Keysym = c_ulong;
|
||||||
|
@ -19,146 +21,192 @@ const FALSE_C: c_int = 0;
|
||||||
const TRUE_C: c_int = 1;
|
const TRUE_C: c_int = 1;
|
||||||
|
|
||||||
impl XDisplay {
|
impl XDisplay {
|
||||||
pub fn open(display_name: Option<String>) -> Self {
|
pub fn open(display_name: Option<String>) -> Self {
|
||||||
let name = CString::new(if let Some(name) = display_name {
|
let name = CString::new(if let Some(name) = display_name {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
env::var("DISPLAY").expect("DISPLAY is not set")
|
env::var("DISPLAY").expect("DISPLAY is not set")
|
||||||
})
|
}).unwrap();
|
||||||
.unwrap();
|
let name_ptr = name.as_bytes().as_ptr();
|
||||||
let name_ptr = name.as_bytes().as_ptr();
|
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
|
||||||
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
|
|
||||||
|
|
||||||
Self { ptr: display_ptr }
|
Self {
|
||||||
}
|
ptr: display_ptr,
|
||||||
|
xrecordctx: RefCell::new(None),
|
||||||
pub fn close(self) {
|
|
||||||
unsafe { XCloseDisplay(self.ptr) };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync(&self) {
|
|
||||||
unsafe {
|
|
||||||
XSync(self.ptr, c_int::from(false));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(&self) {
|
pub fn close(self) {
|
||||||
unsafe {
|
unsafe { XCloseDisplay(self.ptr) };
|
||||||
XFlush(self.ptr);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_default_screen(&self) -> c_int {
|
pub fn sync(&self) {
|
||||||
unsafe { XDefaultScreen(self.ptr) }
|
unsafe {
|
||||||
}
|
XSync(self.ptr, c_int::from(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_root_window(&self, screen_nr: c_int) -> Window {
|
pub fn flush(&self) {
|
||||||
unsafe { XRootWindow(self.ptr, screen_nr) }
|
unsafe {
|
||||||
}
|
XFlush(self.ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn keysym_to_keycode(&self, keysym: c_ulong) -> Keycode {
|
pub fn get_default_screen(&self) -> c_int {
|
||||||
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
|
unsafe { XDefaultScreen(self.ptr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_to_keycode(&self, string: &[u8]) -> Keycode {
|
pub fn get_root_window(&self, screen_nr: c_int) -> Window {
|
||||||
self.keysym_to_keycode(string_to_keysym(string))
|
unsafe { XRootWindow(self.ptr, screen_nr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTest stuff
|
pub fn keysym_to_keycode(&self, keysym: c_ulong) -> Keycode {
|
||||||
pub fn has_xtest(&self) -> bool {
|
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
|
||||||
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 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]) {
|
pub fn enable_context_async(&self,
|
||||||
self.send_fake_keypress_from_keysym(string_to_keysym(string))
|
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
|
||||||
}
|
closure: *mut c_char,
|
||||||
|
) {
|
||||||
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 {
|
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) {
|
pub fn process_replies(&self) {
|
||||||
unsafe { XAllowEvents(self.ptr, event_mode, time) };
|
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!
|
/// Wrapper for XStringToKeysym. Remember to give a null terminated string!
|
||||||
pub fn string_to_keysym(string: &[u8]) -> Keysym {
|
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) }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue