I have achieved recording macros!!!!
This commit is contained in:
parent
8a20bb2d25
commit
f3725a003a
2 changed files with 152 additions and 53 deletions
|
@ -3,8 +3,11 @@ extern crate core;
|
||||||
use std::os::raw::{c_char, c_uchar, c_uint};
|
use std::os::raw::{c_char, c_uchar, c_uint};
|
||||||
use std::process::{exit, ExitCode};
|
use std::process::{exit, ExitCode};
|
||||||
use std::ptr::{addr_of, slice_from_raw_parts};
|
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::ffi::c_void;
|
||||||
|
use std::fmt::{format};
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use x11::keysym::XK_Escape;
|
use x11::keysym::XK_Escape;
|
||||||
use x11::xinput2::XIGrabModeSync;
|
use x11::xinput2::XIGrabModeSync;
|
||||||
|
@ -16,7 +19,7 @@ use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Args {
|
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)]
|
#[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)]
|
||||||
output_file: Option<std::path::PathBuf>,
|
output_file: Option<std::path::PathBuf>,
|
||||||
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
|
/// 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();
|
let screen = display.get_default_screen();
|
||||||
dbg!(stop_key);
|
dbg!(stop_key);
|
||||||
|
|
||||||
ev_loop(display, recorded_display, screen, stop_key);
|
let mut outfile: Box<dyn Write> = 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();
|
display.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,21 +77,37 @@ fn get_stop_key(display: XDisplay) -> Keycode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
display.ungrab_keyboard(CurrentTime);
|
||||||
|
display.ungrab_pointer(CurrentTime);
|
||||||
|
|
||||||
stop_key
|
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<dyn Write>) {
|
||||||
let root = display.get_root_window(screen);
|
let root = display.get_root_window(screen);
|
||||||
let protocol_ranges = unsafe { XRecordAllocRange() };
|
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 ctx = recordeddpy.create_record_context(protocol_ranges);
|
||||||
let ev_cb_data = EvCallbackData {
|
let ev_cb_data = EvCallbackData {
|
||||||
|
outfile,
|
||||||
xdpy: display,
|
xdpy: display,
|
||||||
recdpy: recordeddpy,
|
recdpy: recordeddpy,
|
||||||
ctx,
|
ctx,
|
||||||
stop_key,
|
stop_key,
|
||||||
nr_evs: 0,
|
ev_nr: 0,
|
||||||
working: true
|
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) {
|
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) };
|
unsafe { XFree(protocol_ranges as *mut c_void) };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct EvCallbackData {
|
pub struct EvCallbackData {
|
||||||
pub xdpy: XDisplay,
|
outfile: Box<dyn Write>,
|
||||||
pub recdpy: XDisplay,
|
xdpy: XDisplay,
|
||||||
pub stop_key: Keycode,
|
recdpy: XDisplay,
|
||||||
pub nr_evs: u32,
|
stop_key: Keycode,
|
||||||
pub working: bool,
|
ev_nr: u32,
|
||||||
pub ctx: XRecordContext,
|
working: bool,
|
||||||
// x: i32,
|
ctx: XRecordContext,
|
||||||
// y: i32,
|
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) {
|
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 data = &mut *(closure as *mut EvCallbackData);
|
||||||
let intercept_data = &mut *intercept_data;
|
let intercept_data = &mut *intercept_data;
|
||||||
dbg!(&intercept_data);
|
|
||||||
dbg!(&data);
|
|
||||||
|
|
||||||
if intercept_data.category == XRecordStartOfData {
|
if intercept_data.category == XRecordStartOfData {
|
||||||
println!("Got start of data!");
|
println!("Got start of data!");
|
||||||
|
XRecordFreeData(intercept_data);
|
||||||
return;
|
return;
|
||||||
} else if intercept_data.category == XRecordEndOfData {
|
} else if intercept_data.category == XRecordEndOfData {
|
||||||
println!("Got end of data!");
|
println!("Got end of data!");
|
||||||
|
XRecordFreeData(intercept_data);
|
||||||
return;
|
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);
|
let ev_type = *(intercept_data.data as *const u8);
|
||||||
match ev_type {
|
if data.x == -1 || data.y == -1 {
|
||||||
KEYPRESS_U8 => {
|
if ev_type == MOTIONNOTIFY_U8 {
|
||||||
let kc = *((intercept_data.data as usize + 1) as *const u8);
|
data.x = *((intercept_data.data as usize + std::mem::size_of::<i16>() * 10) as *const i16);
|
||||||
let stop = kc == data.stop_key as u8;
|
data.y = *((intercept_data.data as usize + std::mem::size_of::<i16>() * 11) as *const i16);
|
||||||
if stop {
|
println!("MotionNotify {} {}", data.x, data.y);
|
||||||
println!("stop key detected!");
|
} else {
|
||||||
data.working = false;
|
println!("Move your cursor so the macro can start with a fixed cursor position!");
|
||||||
XRecordFreeData(intercept_data)
|
println!("Skipping event...");
|
||||||
} else {
|
}
|
||||||
// let keyname = data.xdpy.keycode_to_string(kc as u32);
|
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
|
||||||
let keyname = data.xdpy.keycode_to_string(44);
|
println!("Skipping KeyRelease without recorded KeyPress...");
|
||||||
let rstring = format!("KeyPress {}", &keyname.to_str().unwrap());
|
} else {
|
||||||
// let rstring = format!("KeyPress {}", kc);
|
// if
|
||||||
// dbg!(kc);
|
match ev_type {
|
||||||
dbg!(&rstring);
|
MOTIONNOTIFY_U8 => {
|
||||||
std::mem::forget(keyname);
|
let root_x = *((intercept_data.data as usize + std::mem::size_of::<i16>() * 10) as *const i16);
|
||||||
// XRecordFreeData(intercept_data)
|
let root_y = *((intercept_data.data as usize + std::mem::size_of::<i16>() * 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;
|
const KEYPRESS_U8: u8 = KeyPress as u8;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::cell::{Ref, RefCell};
|
use std::cell::{Ref, RefCell};
|
||||||
use std::env;
|
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 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::xrecord::{XRecordAllClients, XRecordAllocRange, XRecordClientInfo, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange};
|
||||||
use x11::xtest::{
|
use x11::xtest::{
|
||||||
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
|
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 {
|
pub fn get_default_screen(&self) -> c_int {
|
||||||
unsafe { XDefaultScreen(self.ptr) }
|
unsafe { XDefaultScreen(self.ptr) }
|
||||||
}
|
}
|
||||||
|
@ -232,11 +252,7 @@ pub fn string_to_keysym(string: &[u8]) -> Keysym {
|
||||||
}
|
}
|
||||||
pub fn keysym_to_string(keysym: Keysym) -> CString {
|
pub fn keysym_to_string(keysym: Keysym) -> CString {
|
||||||
unsafe {
|
unsafe {
|
||||||
let raw = XKeysymToString(keysym);
|
let cstr = CStr::from_ptr(XKeysymToString(keysym));
|
||||||
dbg!(raw);
|
CString::from(cstr)
|
||||||
let r = CString::from_raw(raw);
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
// .into_string()
|
|
||||||
// .expect("failed to convert CString into Rust String")
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue