cleaned up stuff

This commit is contained in:
Schrottkatze 2022-07-21 10:54:56 +02:00
parent ea2245d7b1
commit b9770f43f1
6 changed files with 191 additions and 185 deletions

View file

@ -1,7 +1,6 @@
[package] [package]
name = "easymacros" name = "easymacros"
version = "0.1.0" version = "0.1.0"
author = "obsidianical"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -1,27 +1,18 @@
extern crate core; 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::{io, slice, thread};
use std::borrow::BorrowMut;
use std::cmp::max;
use std::env::args;
use std::io::Write;
use std::ffi::c_void; use std::ffi::c_void;
use std::fmt::format; use std::os::raw::{c_char};
use std::fs::{File, OpenOptions}; use std::process::{exit};
use std::mem::size_of; use std::ptr::addr_of;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use clap::Parser;
use x11::keysym::XK_Escape;
use x11::xinput2::XIGrabModeSync;
use x11::xlib::{ButtonPress, ButtonRelease, CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyCode, KeyPress, KeyPressMask, KeyRelease, MotionNotify, SyncPointer, Time, XEvent, XFree, XKeyEvent, XKeyPressedEvent, XPointer};
use x11::xrecord::{XRecordAllocRange, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData};
use easymacros::{Instructions, Position};
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
const DELAY_DEFAULT_MS: u16 = 10; use clap::Parser;
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};
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro. /// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -54,7 +45,7 @@ fn main() {
recorded_display, recorded_display,
stop_key, stop_key,
writer, writer,
args.max_delay args.max_delay,
); );
display.close(); display.close();
@ -73,7 +64,7 @@ fn get_stop_key(display: XDisplay) -> Keycode {
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 mut stop_key: Keycode = XK_Escape; let stop_key;
loop { loop {
display.allow_events(SyncPointer, CurrentTime); display.allow_events(SyncPointer, CurrentTime);
let ev = XKeyEvent::from(display.window_event(root, KeyPressMask)); let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
@ -92,14 +83,13 @@ fn event_loop(
recdpy: XDisplay, recdpy: XDisplay,
stop_key: Keycode, stop_key: Keycode,
mut writer: MacroWriter, mut writer: MacroWriter,
max_delay: Option<Time> max_delay: Option<Time>,
) { ) {
// let root = display.get_root_window(screen);
let protocol_ranges = unsafe { XRecordAllocRange() }; let protocol_ranges = unsafe { XRecordAllocRange() };
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos()); let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
if pointer_pos != Position (-1, -1) { if pointer_pos != Position(-1, -1) {
writer.write(Instructions::MotionNotify(pointer_pos)) writer.write(Instructions::MotionNotify(pointer_pos))
} }
@ -118,69 +108,6 @@ fn event_loop(
unsafe { XFree(protocol_ranges as *mut c_void) }; unsafe { XFree(protocol_ranges as *mut c_void) };
} }
#[repr(C)]
pub struct EvCallbackData {
pub writer: MacroWriter,
pub xdpy: XDisplay,
pub recdpy: XDisplay,
pub ctx: XRecordContext,
stop_key: Keycode,
ev_nr: u32,
working: bool,
pos: Position<i16>,
max_delay: Option<Time>,
no_keypress_yet: bool,
last_event: Time,
moving: bool,
}
impl EvCallbackData {
pub fn new(
writer: MacroWriter,
xdpy: XDisplay,
recdpy: XDisplay,
ctx: XRecordContext,
stop_key: Keycode,
pos: Position<i16>,
max_delay: Option<Time>,
) -> Self {
EvCallbackData {
writer,
xdpy,
recdpy,
ctx,
stop_key,
ev_nr: 0,
working: true,
pos,
max_delay,
no_keypress_yet: true,
last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
moving: false,
}
}
pub fn ptr_is_moving(&self) -> bool { self.moving }
pub unsafe fn update_pos(&mut self, intercept_data: &mut XRecordInterceptData) -> Position<i16> {
self.pos.0 = *((intercept_data.data as usize + size_of::<i16>() * 10) as *const i16);
self.pos.1 = *((intercept_data.data as usize + size_of::<i16>() * 11) as *const i16);
self.pos
}
pub fn write_pos(&mut self) {
self.writer.write(Instructions::MotionNotify(self.pos));
self.moving = false;
}
pub fn maybe_write_delay(&mut self, server_time: Time) {
if server_time - self.last_event > 1 {
self.writer.write(Instructions::Delay(calculate_delay(server_time, self.last_event, self.max_delay)));
self.last_event = server_time;
}
}
}
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) {
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;
@ -209,7 +136,6 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 { } else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
println!("Skipping KeyRelease without recorded KeyPress..."); println!("Skipping KeyRelease without recorded KeyPress...");
} else { } else {
// if
match ev_type { match ev_type {
MOTIONNOTIFY_U8 => { MOTIONNOTIFY_U8 => {
data.update_pos(intercept_data); data.update_pos(intercept_data);
@ -234,10 +160,10 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
if data.ptr_is_moving() { data.write_pos(); } if data.ptr_is_moving() { data.write_pos(); }
data.writer.write(if ev_type == KEYPRESS_U8 { data.writer.write(if ev_type == KEYPRESS_U8 {
Instructions::KeyStrPress(keyname) Instructions::KeyStrPress(keyname)
} else { } else {
Instructions::KeyStrRelease(keyname) Instructions::KeyStrRelease(keyname)
}); });
} }
} }
BUTTONPRESS_U8 | BUTTONRELEASE_U8 => { BUTTONPRESS_U8 | BUTTONRELEASE_U8 => {
@ -247,11 +173,11 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
if data.ptr_is_moving() { data.write_pos(); } if data.ptr_is_moving() { data.write_pos(); }
if ev_type == BUTTONPRESS_U8 { data.writer.write(if ev_type == BUTTONPRESS_U8 {
data.writer.write(Instructions::ButtonPress(bc)); Instructions::ButtonPress(bc)
} else { } else {
data.writer.write(Instructions::ButtonRelease(bc)); Instructions::ButtonRelease(bc)
} });
} }
_ => eprintln!("Unknown event type: {:?}", ev_type) _ => eprintln!("Unknown event type: {:?}", ev_type)
} }
@ -261,52 +187,3 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
XRecordFreeData(intercept_data) XRecordFreeData(intercept_data)
} }
fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>) -> Time {
if let Some(max) = max_delay {
let max = max as u64;
let delay = server_time - last_event;
if delay > max {
max
} else {
delay
}
} else {
server_time - last_event
}
}
pub struct MacroWriter {
outfile: Box<dyn Write>,
ignore_delay_capturing: bool,
}
impl MacroWriter {
pub fn new(outfile: Option<std::path::PathBuf>, ignore_delay_capturing: bool) -> Self {
Self {
outfile: if let Some(outfile) = outfile {
Box::new(File::create(outfile).expect("Failed to create output file"))
} else {
Box::new(io::stdout())
},
ignore_delay_capturing,
}
}
pub fn write(&mut self, instruction: Instructions) {
if self.ignore_delay_capturing {
if let Instructions::Delay(_) = instruction {
return;
}
}
writeln!(&mut self.outfile, "{}", instruction).expect("Failed to write instruction to outfile");
}
}
const KEYPRESS_U8: u8 = KeyPress as u8;
const KEYRELEASE_U8: u8 = KeyRelease as u8;
const BUTTONPRESS_U8: u8 = ButtonPress as u8;
const BUTTONRELEASE_U8: u8 = ButtonRelease as u8;
const MOTIONNOTIFY_U8: u8 = MotionNotify as u8;

86
src/ev_callback_data.rs Normal file
View file

@ -0,0 +1,86 @@
use std::mem::size_of;
use std::time::{SystemTime, UNIX_EPOCH};
use x11::xlib::Time;
use x11::xrecord::{XRecordContext, XRecordInterceptData};
use crate::{Instructions, Keycode, Position};
use crate::macro_writer::MacroWriter;
use crate::x11_safe_wrapper::XDisplay;
#[repr(C)]
pub struct EvCallbackData {
pub writer: MacroWriter,
pub xdpy: XDisplay,
pub recdpy: XDisplay,
pub ctx: XRecordContext,
pub working: bool,
pub last_event: Time,
pub pos: Position<i16>,
pub stop_key: Keycode,
pub ev_nr: u32,
pub max_delay: Option<Time>,
pub no_keypress_yet: bool,
pub moving: bool,
}
impl EvCallbackData {
pub fn new(
writer: MacroWriter,
xdpy: XDisplay,
recdpy: XDisplay,
ctx: XRecordContext,
stop_key: Keycode,
pos: Position<i16>,
max_delay: Option<Time>,
) -> Self {
EvCallbackData {
writer,
xdpy,
recdpy,
ctx,
stop_key,
ev_nr: 0,
working: true,
pos,
max_delay,
no_keypress_yet: true,
last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
moving: false,
}
}
pub fn ptr_is_moving(&self) -> bool { self.moving }
pub unsafe fn update_pos(&mut self, intercept_data: &mut XRecordInterceptData) -> Position<i16> {
self.pos.0 = *((intercept_data.data as usize + size_of::<i16>() * 10) as *const i16);
self.pos.1 = *((intercept_data.data as usize + size_of::<i16>() * 11) as *const i16);
self.pos
}
pub fn write_pos(&mut self) {
self.writer.write(Instructions::MotionNotify(self.pos));
self.moving = false;
}
pub fn maybe_write_delay(&mut self, server_time: Time) {
if server_time - self.last_event > 1 {
self.writer.write(Instructions::Delay(calculate_delay(server_time, self.last_event, self.max_delay)));
self.last_event = server_time;
}
}
}
fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>) -> Time {
if let Some(max) = max_delay {
let max = max as u64;
let delay = server_time - last_event;
if delay > max {
max
} else {
delay
}
} else {
server_time - last_event
}
}

View file

@ -2,12 +2,21 @@ extern crate core;
use std::ffi::CString; use std::ffi::CString;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::time::Duration;
use x11::xlib::Time; use x11::xlib::{ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify, Time};
use crate::x11_safe_wrapper::{Keycode, Keysym}; use crate::x11_safe_wrapper::{Keycode, Keysym};
pub mod x11_safe_wrapper; pub mod x11_safe_wrapper;
pub mod chartbl; pub mod chartbl;
pub mod macro_writer;
pub mod ev_callback_data;
pub const KEYPRESS_U8: u8 = KeyPress as u8;
pub const KEYRELEASE_U8: u8 = KeyRelease as u8;
pub const BUTTONPRESS_U8: u8 = ButtonPress as u8;
pub const BUTTONRELEASE_U8: u8 = ButtonRelease as u8;
pub const MOTIONNOTIFY_U8: u8 = MotionNotify as u8;
#[derive(Copy, Clone, PartialEq, Eq)] #[derive(Copy, Clone, PartialEq, Eq)]
pub struct Position<T> (pub T, pub T); pub struct Position<T> (pub T, pub T);

33
src/macro_writer.rs Normal file
View file

@ -0,0 +1,33 @@
use std::fs::File;
use std::io;
use std::io::Write;
use crate::Instructions;
pub struct MacroWriter {
outfile: Box<dyn Write>,
ignore_delay_capturing: bool,
}
impl MacroWriter {
pub fn new(outfile: Option<std::path::PathBuf>, ignore_delay_capturing: bool) -> Self {
Self {
outfile: if let Some(outfile) = outfile {
Box::new(File::create(outfile).expect("Failed to create output file"))
} else {
Box::new(io::stdout())
},
ignore_delay_capturing,
}
}
pub fn write(&mut self, instruction: Instructions) {
if self.ignore_delay_capturing {
if let Instructions::Delay(_) = instruction {
return;
}
}
writeln!(&mut self.outfile, "{}", instruction).expect("Failed to write instruction to outfile");
}
}

View file

@ -1,13 +1,13 @@
use std::{env, slice}; use std::{env, slice};
use std::ffi::{CStr, 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, KeyPress, MotionNotify, Time, Window, XAllowEvents, XCloseDisplay, XConvertCase, XDefaultScreen, XEvent, XFlush, XGetKeyboardMapping, XGrabKeyboard, XKeycodeToKeysym, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XQueryPointer, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent}; use x11::xlib::{Display, GenericEvent, KeyPress, MotionNotify, Time, Window, XAllowEvents, XCloseDisplay, XConvertCase, XDefaultScreen, XEvent, XFlush, XGetKeyboardMapping, XGrabKeyboard, XKeycodeToKeysym, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XQueryPointer, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent};
use x11::xrecord::{XRecordAllClients, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange}; use x11::xrecord::{XRecordAllClients, 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,
XTestQueryExtension, XTestQueryExtension,
}; };
use crate::Instructions::KeySym;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct XDisplay { pub struct XDisplay {
@ -54,7 +54,8 @@ impl XDisplay {
let mut unneeded_ints: (i32, i32) = (0, 0); let mut unneeded_ints: (i32, i32) = (0, 0);
let mut unneeded_wins: (u64, u64) = (0, 0); let mut unneeded_wins: (u64, u64) = (0, 0);
let mut unneeded_mask: u32 = 0; let mut unneeded_mask: u32 = 0;
unsafe { XQueryPointer( unsafe {
XQueryPointer(
self.ptr, self.ptr,
self.get_root_window(self.get_default_screen()), self.get_root_window(self.get_default_screen()),
&mut unneeded_wins.0, &mut unneeded_wins.0,
@ -63,8 +64,9 @@ impl XDisplay {
&mut r.1, &mut r.1,
&mut unneeded_ints.0, &mut unneeded_ints.0,
&mut unneeded_ints.1, &mut unneeded_ints.1,
&mut unneeded_mask &mut unneeded_mask,
) }; )
};
r r
} }
@ -84,7 +86,7 @@ impl XDisplay {
self.ptr, self.ptr,
keycode as c_uchar, keycode as c_uchar,
keycode_count, keycode_count,
&mut keysyms_per_keycode &mut keysyms_per_keycode,
); );
slice::from_raw_parts::<Keysym>(ptr, keysyms_per_keycode as usize) slice::from_raw_parts::<Keysym>(ptr, keysyms_per_keycode as usize)
@ -183,13 +185,13 @@ impl XDisplay {
pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 { pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 {
unsafe { unsafe {
XGrabKeyboard( XGrabKeyboard(
self.ptr, self.ptr,
window, window,
c_int::from(owner_events), c_int::from(owner_events),
pointer_mode, pointer_mode,
keyboard_mode, keyboard_mode,
time, time,
) )
} }
} }
@ -221,56 +223,56 @@ impl XDisplay {
(*protocol_ranges).device_events.first = KeyPress as c_uchar; (*protocol_ranges).device_events.first = KeyPress as c_uchar;
(*protocol_ranges).device_events.last = MotionNotify as c_uchar; (*protocol_ranges).device_events.last = MotionNotify as c_uchar;
} }
let mut clients: XRecordClientSpec = XRecordAllClients; let mut clients: XRecordClientSpec = XRecordAllClients;
let ctx: XRecordContext = unsafe { let ctx: XRecordContext = unsafe {
XRecordCreateContext( XRecordCreateContext(
self.ptr, self.ptr,
0, 0,
&mut clients, &mut clients,
1, 1,
&mut protocol_ranges, &mut protocol_ranges,
1 1,
) )
}; };
ctx ctx
} }
pub fn enable_context(&self, pub fn enable_context(&self,
ctx: XRecordContext, ctx: XRecordContext,
cb:Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
closure: *mut c_char closure: *mut c_char,
) -> bool { ) -> bool {
unsafe { unsafe {
XRecordEnableContext( self.ptr, ctx, cb, closure as *mut c_char ) != 0 XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0
} }
} }
pub fn enable_context_async(&self, pub fn enable_context_async(&self,
ctx: XRecordContext, ctx: XRecordContext,
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
closure: *mut c_char, closure: *mut c_char,
) -> bool { ) -> bool {
unsafe { unsafe {
XRecordEnableContextAsync( self.ptr, ctx, cb, closure as *mut c_char ) != 0 XRecordEnableContextAsync(self.ptr, ctx, cb, closure as *mut c_char) != 0
} }
} }
pub fn disable_context(&self, ctx: XRecordContext) -> bool { pub fn disable_context(&self, ctx: XRecordContext) -> bool {
unsafe { unsafe {
XRecordDisableContext( self.ptr, ctx ) != 0 XRecordDisableContext(self.ptr, ctx) != 0
} }
} }
pub fn free_context(&self, ctx: XRecordContext) -> bool { pub fn free_context(&self, ctx: XRecordContext) -> bool {
unsafe { unsafe {
XRecordFreeContext( self.ptr, ctx) != 0 XRecordFreeContext(self.ptr, ctx) != 0
} }
} }
pub fn process_replies(&self) { pub fn process_replies(&self) {
unsafe { XRecordProcessReplies(self.ptr) }; unsafe { XRecordProcessReplies(self.ptr) };
} }
} }
/// Wrapper for XStringToKeysym. Remember to give a null terminated string! /// Wrapper for XStringToKeysym. Remember to give a null terminated string!