cargo fmt

This commit is contained in:
Schrottkatze 2022-10-10 20:03:00 +02:00
parent 16c0826d3a
commit 801f8b6265
10 changed files with 887 additions and 818 deletions

7
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "anyhow"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -68,6 +74,7 @@ dependencies = [
name = "easymacros" name = "easymacros"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow",
"clap", "clap",
"x11", "x11",
] ]

View file

@ -6,5 +6,6 @@ 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
[dependencies] [dependencies]
anyhow = "1.0.65"
x11 = { version = "2.19.1", features = ["xlib", "xtest"] } x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
clap = { version = "3.2.4", features = ["derive"] } clap = { version = "3.2.4", features = ["derive"] }

View file

@ -36,6 +36,7 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
- [ ] make modes listen for numbers/amounts of repetitions - [ ] make modes listen for numbers/amounts of repetitions
- [ ] make shortcuts be able to listen for other shortcuts and inputs - [ ] make shortcuts be able to listen for other shortcuts and inputs
- [ ] rofi integration - [ ] rofi integration
- [ ] autorun stuff on some windows?
- [ ] Proper, safe xlib wrapper - [ ] Proper, safe xlib wrapper
# #

View file

@ -1,146 +1,173 @@
use clap::Parser; use clap::Parser;
use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay}; use easymacros::chartbl::CHARTBL;
use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay};
use std::ffi::CString; use std::ffi::CString;
use std::process::{Command, exit}; use std::io::stdin;
use std::process::{exit, Command};
use std::time::Duration; use std::time::Duration;
use std::{fs, thread}; use std::{fs, thread};
use std::io::stdin;
use x11::keysym::XK_Shift_L; use x11::keysym::XK_Shift_L;
use easymacros::chartbl::CHARTBL;
/// Macro player module for easymacros. It's compatible with xmacro macros. /// Macro player module for easymacros. It's compatible with xmacro macros.
#[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 that contains the macro to run.
#[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)] #[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)]
input_file: Option<std::path::PathBuf>, input_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.
#[clap(short = 'D', long)] #[clap(short = 'D', long)]
display: Option<String>, display: Option<String>,
/// Delay for events to be sent. /// Delay for events to be sent.
#[clap(short, long)] #[clap(short, long)]
delay: Option<u64>, delay: Option<u64>,
} }
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
let display = get_remote(args.display); let display = get_remote(args.display);
let delay = args.delay.unwrap_or(10); let delay = args.delay.unwrap_or(10);
if let Some(input_file_path) = args.input_file { if let Some(input_file_path) = args.input_file {
let input_file_contents = fs::read_to_string(input_file_path).expect("Couldn't read macro file"); let input_file_contents =
fs::read_to_string(input_file_path).expect("Couldn't read macro file");
for instruction in input_file_contents.lines() { for instruction in input_file_contents.lines() {
run_instruction(instruction, &display, delay); run_instruction(instruction, &display, delay);
} }
} else { } else {
println!("No input file specified, reading from stdin."); println!("No input file specified, reading from stdin.");
let stdin = stdin(); let stdin = stdin();
loop { loop {
let mut line = String::new(); let mut line = String::new();
stdin.read_line(&mut line).expect("Couldn't read line from stdin"); stdin
// Without this it crashes because apparently it doesn't properly read the next input line? .read_line(&mut line)
println!(); .expect("Couldn't read line from stdin");
line = line.trim().to_string(); // Without this it crashes because apparently it doesn't properly read the next input line?
run_instruction(&*line, &display, delay); println!();
} line = line.trim().to_string();
} run_instruction(&*line, &display, delay);
}
}
display.close(); display.close();
} }
fn get_remote(display_name: Option<String>) -> XDisplay { fn get_remote(display_name: Option<String>) -> XDisplay {
let display = XDisplay::open(display_name); let display = XDisplay::open(display_name);
if !display.has_xtest() { if !display.has_xtest() {
eprintln!("XTest not supported!"); eprintln!("XTest not supported!");
display.close(); display.close();
exit(1) exit(1)
} }
display.grab_control(); display.grab_control();
display.sync(); display.sync();
display display
} }
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) { fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
let instruction_split: Vec<&str> = instruction.split(' ').collect(); let instruction_split: Vec<&str> = instruction.split(' ').collect();
match instruction_split[0] { match instruction_split[0] {
"Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())), "Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
"ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay), "ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
"ButtonRelease" => dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay), "ButtonRelease" => {
"MotionNotify" => dpy.send_fake_motion_event(instruction_split[1].parse().unwrap(), instruction_split[2].parse().unwrap(), delay), dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay)
"KeyCodePress" => dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay), }
"KeyCodeRelease" => dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay), "MotionNotify" => dpy.send_fake_motion_event(
"KeySymPress" => dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay), instruction_split[1].parse().unwrap(),
"KeySymRelease" => dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay), instruction_split[2].parse().unwrap(),
delay,
),
"KeyCodePress" => {
dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay)
}
"KeyCodeRelease" => {
dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay)
}
"KeySymPress" => {
dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay)
}
"KeySymRelease" => {
dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay)
}
"KeySym" => { "KeySym" => {
let key: Keysym = instruction_split[1].parse().unwrap(); let key: Keysym = instruction_split[1].parse().unwrap();
dpy.send_fake_keypress_from_keysym(key, delay); dpy.send_fake_keypress_from_keysym(key, delay);
dpy.send_fake_keyrelease_from_keysym(key, delay); dpy.send_fake_keyrelease_from_keysym(key, delay);
} }
"KeyStrPress" => dpy.send_fake_keypress_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay), "KeyStrPress" => dpy.send_fake_keypress_from_string(
"KeyStrRelease" => dpy.send_fake_keyrelease_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay), CString::new(instruction_split[1]).unwrap().as_bytes(),
"KeyStr" => { delay,
let keystring = CString::new(instruction_split[1]).unwrap(); ),
dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay); "KeyStrRelease" => dpy.send_fake_keyrelease_from_string(
dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay); CString::new(instruction_split[1]).unwrap().as_bytes(),
} delay,
"String" => { ),
for c in instruction["String".len() + 1..].chars() { "KeyStr" => {
send_char(dpy, c, delay); let keystring = CString::new(instruction_split[1]).unwrap();
} dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
} dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
"ExecBlock" | "ExecNoBlock" => { }
let mut command = Command::new(instruction_split[1]); "String" => {
for arg in &instruction_split[2..] { for c in instruction["String".len() + 1..].chars() {
command.arg(arg); send_char(dpy, c, delay);
} }
if instruction_split[0] == "ExecBlock" { }
command.status().unwrap(); "ExecBlock" | "ExecNoBlock" => {
} else { let mut command = Command::new(instruction_split[1]);
command.spawn().unwrap(); for arg in &instruction_split[2..] {
} command.arg(arg);
} }
c => panic!("Unknown command {:?}", instruction_split), if instruction_split[0] == "ExecBlock" {
} command.status().unwrap();
} else {
command.spawn().unwrap();
}
}
c => panic!("Unknown command {:?}", instruction_split),
}
} }
fn send_char(dpy: &XDisplay, c: char, delay: u64) { fn send_char(dpy: &XDisplay, c: char, delay: u64) {
// get keystring from character and turn it into a keysym // get keystring from character and turn it into a keysym
let keysym = string_to_keysym(CHARTBL[c as usize].as_ref()); let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
let keycode = dpy.keysym_to_keycode(keysym); let keycode = dpy.keysym_to_keycode(keysym);
if keycode == 0 { if keycode == 0 {
eprintln!("No keycode found for character '{}'", c); eprintln!("No keycode found for character '{}'", c);
return; return;
} }
let map_ks = dpy.get_keyboard_mapping(keycode, 1); let map_ks = dpy.get_keyboard_mapping(keycode, 1);
if map_ks[0] == 0 { if map_ks[0] == 0 {
eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode); eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
return; return;
} }
let (ks_lower, ks_upper) = dpy.convert_case(keysym); let (ks_lower, ks_upper) = dpy.convert_case(keysym);
// check if shift has to be pressed as well // check if shift has to be pressed as well
let mut shift_needed = true; let mut shift_needed = true;
if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) { if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
shift_needed = false; shift_needed = false;
} }
if keysym == ks_lower && keysym != ks_upper { if keysym == ks_lower && keysym != ks_upper {
shift_needed = false; shift_needed = false;
} }
if shift_needed { dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); } if shift_needed {
dpy.send_fake_keypress_from_code(keycode, delay); dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay);
dpy.send_fake_keyrelease_from_code(keycode, delay); }
if shift_needed { dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); } dpy.send_fake_keypress_from_code(keycode, delay);
dpy.send_fake_keyrelease_from_code(keycode, delay);
if shift_needed {
dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay);
}
} }

View file

@ -1,189 +1,198 @@
extern crate core; extern crate core;
use std::ffi::c_void; use std::ffi::c_void;
use std::os::raw::{c_char}; use std::os::raw::c_char;
use std::process::{exit}; use std::process::exit;
use std::ptr::addr_of; use std::ptr::addr_of;
use clap::Parser; use clap::Parser;
use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, XKeyEvent}; use x11::xlib::{
use x11::xrecord::{XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData}; 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::ev_callback_data::EvCallbackData;
use easymacros::macro_writer::MacroWriter; use easymacros::macro_writer::MacroWriter;
use easymacros::x11_safe_wrapper::{Keycode, XDisplay}; use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
use easymacros::{
Instructions, Position, BUTTONPRESS_U8, BUTTONRELEASE_U8, KEYPRESS_U8, KEYRELEASE_U8,
MOTIONNOTIFY_U8,
};
/// 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)]
#[clap(author, version, about, long_about = None)] #[clap(author, version, about, long_about = None)]
struct Args { struct Args {
/// The file to record the macro to. Defaults to writing to stdout. /// 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.
#[clap(short = 'D', long)] #[clap(short = 'D', long)]
display: Option<String>, display: Option<String>,
/// Max Delay in milliseconds for macro delays /// Max Delay in milliseconds for macro delays
#[clap(short, long)] #[clap(short, long)]
max_delay: Option<u64>, max_delay: Option<u64>,
/// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay. /// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay.
#[clap(short, long)] #[clap(short, long)]
ignore_delay_capturing: bool, ignore_delay_capturing: bool,
} }
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
let display = XDisplay::open(args.display.clone()); let display = XDisplay::open(args.display.clone());
let recorded_display = XDisplay::open(args.display.clone()); let recorded_display = XDisplay::open(args.display.clone());
let stop_key = get_stop_key(display); let stop_key = get_stop_key(display);
let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing); let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
event_loop( event_loop(display, recorded_display, stop_key, writer, args.max_delay);
display,
recorded_display,
stop_key,
writer,
args.max_delay,
);
display.close(); display.close();
} }
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);
let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime); let potential_err =
display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
if potential_err != GrabSuccess { if potential_err != GrabSuccess {
eprintln!("Couldn't grab keyboard!"); eprintln!("Couldn't grab keyboard!");
exit(1); exit(1);
} }
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 stop_key; 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));
stop_key = ev.keycode; stop_key = ev.keycode;
break; break;
} }
display.ungrab_keyboard(CurrentTime); display.ungrab_keyboard(CurrentTime);
display.ungrab_pointer(CurrentTime); display.ungrab_pointer(CurrentTime);
stop_key stop_key
} }
fn event_loop( fn event_loop(
xdpy: XDisplay, xdpy: XDisplay,
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 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))
} }
let ctx = recdpy.create_record_context(protocol_ranges); let ctx = recdpy.create_record_context(protocol_ranges);
let ev_cb_data = EvCallbackData::new(writer, xdpy, recdpy, ctx, stop_key, pointer_pos, max_delay); let ev_cb_data =
EvCallbackData::new(writer, xdpy, recdpy, ctx, stop_key, pointer_pos, max_delay);
if !recdpy.enable_context_async(ctx, Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char) { if !recdpy.enable_context_async(ctx, Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char) {
panic!("Failed to enable record context") panic!("Failed to enable record context")
} }
while ev_cb_data.working { while ev_cb_data.working {
recdpy.process_replies(); recdpy.process_replies();
} }
xdpy.disable_context(ctx); xdpy.disable_context(ctx);
xdpy.free_context(ctx); xdpy.free_context(ctx);
unsafe { XFree(protocol_ranges as *mut c_void) }; unsafe { XFree(protocol_ranges as *mut c_void) };
} }
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;
if intercept_data.category == XRecordStartOfData { if intercept_data.category == XRecordStartOfData {
println!("Got start of data!"); println!("Got start of data!");
data.last_event = intercept_data.server_time; data.last_event = intercept_data.server_time;
XRecordFreeData(intercept_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); XRecordFreeData(intercept_data);
return; return;
} }
let ev_type = *(intercept_data.data as *const u8); let ev_type = *(intercept_data.data as *const u8);
if data.pos.0 == 0 || data.pos.1 == -1 { if data.pos.0 == 0 || data.pos.1 == -1 {
if ev_type == MOTIONNOTIFY_U8 { if ev_type == MOTIONNOTIFY_U8 {
data.update_pos(intercept_data); data.update_pos(intercept_data);
data.write_pos(); data.write_pos();
} else { } else {
println!("Move your cursor so the macro can start with a fixed cursor position! println!(
Skipping event..."); "Move your cursor so the macro can start with a fixed cursor position!
} Skipping event..."
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 { );
println!("Skipping KeyRelease without recorded KeyPress..."); }
} else { } else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
match ev_type { println!("Skipping KeyRelease without recorded KeyPress...");
MOTIONNOTIFY_U8 => { } else {
data.update_pos(intercept_data); match ev_type {
MOTIONNOTIFY_U8 => {
data.update_pos(intercept_data);
if !data.moving { if !data.moving {
data.moving = true; data.moving = true;
} }
} }
KEYPRESS_U8 | KEYRELEASE_U8 => { KEYPRESS_U8 | KEYRELEASE_U8 => {
let kc: u8 = *((intercept_data.data as usize + 1) as *const u8); let kc: u8 = *((intercept_data.data as usize + 1) as *const u8);
let keyname = data.xdpy.keycode_to_string(kc as u32); let keyname = data.xdpy.keycode_to_string(kc as u32);
if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 { if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 {
data.working = false; data.working = false;
} else { } else {
if ev_type == KEYPRESS_U8 { if ev_type == KEYPRESS_U8 {
data.no_keypress_yet = false; data.no_keypress_yet = false;
} }
data.maybe_write_delay(intercept_data.server_time); data.maybe_write_delay(intercept_data.server_time);
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 => {
let bc: u8 = *((intercept_data.data as usize + 1) as *const u8); let bc: u8 = *((intercept_data.data as usize + 1) as *const u8);
data.maybe_write_delay(intercept_data.server_time); data.maybe_write_delay(intercept_data.server_time);
if data.ptr_is_moving() { data.write_pos(); } if data.ptr_is_moving() {
data.write_pos();
}
data.writer.write(if ev_type == BUTTONPRESS_U8 { data.writer.write(if ev_type == BUTTONPRESS_U8 {
Instructions::ButtonPress(bc) Instructions::ButtonPress(bc)
} else { } else {
Instructions::ButtonRelease(bc) Instructions::ButtonRelease(bc)
}); });
} }
_ => eprintln!("Unknown event type: {:?}", ev_type) _ => eprintln!("Unknown event type: {:?}", ev_type),
} }
} }
data.ev_nr += 2; data.ev_nr += 2;
XRecordFreeData(intercept_data) XRecordFreeData(intercept_data)
} }

View file

@ -1,259 +1,259 @@
// the list is copied from https://github.com/Ortega-Dan/xmacroIncludingDelayCapturing/blob/1767fd310227dd13ae488b7d2821cc4bbf3847e0/chartbl.h#L27 // the list is copied from https://github.com/Ortega-Dan/xmacroIncludingDelayCapturing/blob/1767fd310227dd13ae488b7d2821cc4bbf3847e0/chartbl.h#L27
pub const CHARTBL: [&str; 256] = [ pub const CHARTBL: [&str; 256] = [
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"BackSpace\0", "BackSpace\0",
"Tab\0", "Tab\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"Return\0", "Return\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"Escape\0", "Escape\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"space\0", "space\0",
"exclam\0", "exclam\0",
"quotedbl\0", "quotedbl\0",
"numbersign\0", "numbersign\0",
"dollar\0", "dollar\0",
"percent\0", "percent\0",
"ampersand\0", "ampersand\0",
"apostrophe\0", "apostrophe\0",
"parenleft\0", "parenleft\0",
"parenright\0", "parenright\0",
"asterisk\0", "asterisk\0",
"plus\0", "plus\0",
"comma\0", "comma\0",
"minus\0", "minus\0",
"period\0", "period\0",
"slash\0", "slash\0",
"0\0", "0\0",
"1\0", "1\0",
"2\0", "2\0",
"3\0", "3\0",
"4\0", "4\0",
"5\0", "5\0",
"6\0", "6\0",
"7\0", "7\0",
"8\0", "8\0",
"9\0", "9\0",
"colon\0", "colon\0",
"semicolon\0", "semicolon\0",
"less\0", "less\0",
"equal\0", "equal\0",
"greater\0", "greater\0",
"question\0", "question\0",
"at\0", "at\0",
"A\0", "A\0",
"B\0", "B\0",
"C\0", "C\0",
"D\0", "D\0",
"E\0", "E\0",
"F\0", "F\0",
"G\0", "G\0",
"H\0", "H\0",
"I\0", "I\0",
"J\0", "J\0",
"K\0", "K\0",
"L\0", "L\0",
"M\0", "M\0",
"N\0", "N\0",
"O\0", "O\0",
"P\0", "P\0",
"Q\0", "Q\0",
"R\0", "R\0",
"S\0", "S\0",
"T\0", "T\0",
"U\0", "U\0",
"V\0", "V\0",
"W\0", "W\0",
"X\0", "X\0",
"Y\0", "Y\0",
"Z\0", "Z\0",
"bracketleft\0", "bracketleft\0",
"backslash\0", "backslash\0",
"bracketright\0", "bracketright\0",
"asciicircum\0", "asciicircum\0",
"underscore\0", "underscore\0",
"grave\0", "grave\0",
"a\0", "a\0",
"b\0", "b\0",
"c\0", "c\0",
"d\0", "d\0",
"e\0", "e\0",
"f\0", "f\0",
"g\0", "g\0",
"h\0", "h\0",
"i\0", "i\0",
"j\0", "j\0",
"k\0", "k\0",
"l\0", "l\0",
"m\0", "m\0",
"n\0", "n\0",
"o\0", "o\0",
"p\0", "p\0",
"q\0", "q\0",
"r\0", "r\0",
"s\0", "s\0",
"t\0", "t\0",
"u\0", "u\0",
"v\0", "v\0",
"w\0", "w\0",
"x\0", "x\0",
"y\0", "y\0",
"z\0", "z\0",
"braceleft\0", "braceleft\0",
"bar\0", "bar\0",
"braceright\0", "braceright\0",
"asciitilde\0", "asciitilde\0",
"Delete\0", "Delete\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"\0", "\0",
"nobreakspace\0", "nobreakspace\0",
"exclamdown\0", "exclamdown\0",
"cent\0", "cent\0",
"sterling\0", "sterling\0",
"currency\0", "currency\0",
"yen\0", "yen\0",
"brokenbar\0", "brokenbar\0",
"section\0", "section\0",
"diaeresis\0", "diaeresis\0",
"copyright\0", "copyright\0",
"ordfeminine\0", "ordfeminine\0",
"guillemotleft\0", "guillemotleft\0",
"notsign\0", "notsign\0",
"hyphen\0", "hyphen\0",
"registered\0", "registered\0",
"macron\0", "macron\0",
"degree\0", "degree\0",
"plusminus\0", "plusminus\0",
"twosuperior\0", "twosuperior\0",
"threesuperior\0", "threesuperior\0",
"acute\0", "acute\0",
"mu\0", "mu\0",
"paragraph\0", "paragraph\0",
"periodcentered\0", "periodcentered\0",
"cedilla\0", "cedilla\0",
"onesuperior\0", "onesuperior\0",
"masculine\0", "masculine\0",
"guillemotright\0", "guillemotright\0",
"onequarter\0", "onequarter\0",
"onehalf\0", "onehalf\0",
"threequarters\0", "threequarters\0",
"questiondown\0", "questiondown\0",
"Agrave\0", "Agrave\0",
"Aacute\0", "Aacute\0",
"Acircumflex\0", "Acircumflex\0",
"Atilde\0", "Atilde\0",
"Adiaeresis\0", "Adiaeresis\0",
"Aring\0", "Aring\0",
"AE\0", "AE\0",
"Ccedilla\0", "Ccedilla\0",
"Egrave\0", "Egrave\0",
"Eacute\0", "Eacute\0",
"Ecircumflex\0", "Ecircumflex\0",
"Ediaeresis\0", "Ediaeresis\0",
"Igrave\0", "Igrave\0",
"Iacute\0", "Iacute\0",
"Icircumflex\0", "Icircumflex\0",
"Idiaeresis\0", "Idiaeresis\0",
"ETH\0", "ETH\0",
"Ntilde\0", "Ntilde\0",
"Ograve\0", "Ograve\0",
"Oacute\0", "Oacute\0",
"Ocircumflex\0", "Ocircumflex\0",
"Otilde\0", "Otilde\0",
"Odiaeresis\0", "Odiaeresis\0",
"multiply\0", "multiply\0",
"Ooblique\0", "Ooblique\0",
"Ugrave\0", "Ugrave\0",
"Uacute\0", "Uacute\0",
"Ucircumflex\0", "Ucircumflex\0",
"Udiaeresis\0", "Udiaeresis\0",
"Yacute\0", "Yacute\0",
"THORN\0", "THORN\0",
"ssharp\0", "ssharp\0",
"agrave\0", "agrave\0",
"aacute\0", "aacute\0",
"acircumflex\0", "acircumflex\0",
"atilde\0", "atilde\0",
"adiaeresis\0", "adiaeresis\0",
"aring\0", "aring\0",
"ae\0", "ae\0",
"ccedilla\0", "ccedilla\0",
"egrave\0", "egrave\0",
"eacute\0", "eacute\0",
"ecircumflex\0", "ecircumflex\0",
"ediaeresis\0", "ediaeresis\0",
"igrave\0", "igrave\0",
"iacute\0", "iacute\0",
"icircumflex\0", "icircumflex\0",
"idiaeresis\0", "idiaeresis\0",
"eth\0", "eth\0",
"ntilde\0", "ntilde\0",
"ograve\0", "ograve\0",
"oacute\0", "oacute\0",
"ocircumflex\0", "ocircumflex\0",
"otilde\0", "otilde\0",
"odiaeresis\0", "odiaeresis\0",
"division\0", "division\0",
"oslash\0", "oslash\0",
"ugrave\0", "ugrave\0",
"uacute\0", "uacute\0",
"ucircumflex\0", "ucircumflex\0",
"udiaeresis\0", "udiaeresis\0",
"yacute\0", "yacute\0",
"thorn\0", "thorn\0",
"ydiaeresis\0", "ydiaeresis\0",
]; ];

View file

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

View file

@ -7,10 +7,12 @@ use x11::xlib::{ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify,
use crate::x11_safe_wrapper::{Keycode, Keysym}; use crate::x11_safe_wrapper::{Keycode, Keysym};
pub mod x11_safe_wrapper; pub mod xwrap;
pub mod chartbl; pub mod chartbl;
pub mod macro_writer;
pub mod ev_callback_data; pub mod ev_callback_data;
pub mod macro_writer;
pub mod x11_safe_wrapper;
pub const KEYPRESS_U8: u8 = KeyPress as u8; pub const KEYPRESS_U8: u8 = KeyPress as u8;
pub const KEYRELEASE_U8: u8 = KeyRelease as u8; pub const KEYRELEASE_U8: u8 = KeyRelease as u8;
@ -19,11 +21,11 @@ pub const BUTTONRELEASE_U8: u8 = ButtonRelease as u8;
pub const MOTIONNOTIFY_U8: u8 = MotionNotify 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);
impl From<Position<i32>> for Position<i16> { impl From<Position<i32>> for Position<i16> {
fn from(pos: Position<i32>) -> Self { fn from(pos: Position<i32>) -> Self {
Self (pos.0 as i16, pos.1 as i16) Self(pos.0 as i16, pos.1 as i16)
} }
} }
@ -60,7 +62,9 @@ pub enum Instructions<'a> {
impl Display for Instructions<'_> { impl Display for Instructions<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", write!(
f,
"{}",
match self { match self {
Instructions::Delay(d) => format!("Delay {}", d), Instructions::Delay(d) => format!("Delay {}", d),
Instructions::ButtonPress(b) => format!("ButtonPress {}", b), Instructions::ButtonPress(b) => format!("ButtonPress {}", b),
@ -71,8 +75,10 @@ impl Display for Instructions<'_> {
Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks), Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks),
Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks), Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
Instructions::KeySym(ks) => format!("KeySym {}", ks), Instructions::KeySym(ks) => format!("KeySym {}", ks),
Instructions::KeyStrPress(kstr) => format!("KeyStrPress {}", kstr.to_str().unwrap()), Instructions::KeyStrPress(kstr) =>
Instructions::KeyStrRelease(kstr) => format!("KeyStrRelease {}", kstr.to_str().unwrap()), format!("KeyStrPress {}", kstr.to_str().unwrap()),
Instructions::KeyStrRelease(kstr) =>
format!("KeyStrRelease {}", kstr.to_str().unwrap()),
Instructions::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()), Instructions::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()),
Instructions::String(str) => format!("String {}", str), Instructions::String(str) => format!("String {}", str),
Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd), Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd),

View file

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

View file

@ -1,17 +1,26 @@
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 std::{env, slice};
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::{
use x11::xrecord::{XRecordAllClients, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange}; 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::xtest::{ use x11::xtest::{
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl, XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
XTestQueryExtension, XTestQueryExtension,
}; };
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct XDisplay { pub struct XDisplay {
ptr: *mut Display, ptr: *mut Display,
} }
pub type Keysym = c_ulong; pub type Keysym = c_ulong;
@ -21,268 +30,266 @@ 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(); })
let name_ptr = name.as_bytes().as_ptr(); .unwrap();
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) }; let name_ptr = name.as_bytes().as_ptr();
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
Self { ptr: display_ptr } Self { ptr: display_ptr }
} }
pub fn close(self) { pub fn close(self) {
unsafe { XCloseDisplay(self.ptr) }; unsafe { XCloseDisplay(self.ptr) };
} }
pub fn sync(&self) { pub fn sync(&self) {
unsafe { unsafe {
XSync(self.ptr, c_int::from(false)); XSync(self.ptr, c_int::from(false));
} }
} }
pub fn flush(&self) { pub fn flush(&self) {
unsafe { unsafe {
XFlush(self.ptr); XFlush(self.ptr);
} }
} }
pub fn query_pointer_pos(&self) -> (i32, i32) { pub fn query_pointer_pos(&self) -> (i32, i32) {
let mut r: (i32, i32) = (-1, -1); let mut r: (i32, i32) = (-1, -1);
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 { unsafe {
XQueryPointer( 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,
&mut unneeded_wins.1, &mut unneeded_wins.1,
&mut r.0, &mut r.0,
&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
} }
pub fn get_default_screen(&self) -> c_int { pub fn get_default_screen(&self) -> c_int {
unsafe { XDefaultScreen(self.ptr) } unsafe { XDefaultScreen(self.ptr) }
} }
pub fn get_root_window(&self, screen_nr: c_int) -> Window { pub fn get_root_window(&self, screen_nr: c_int) -> Window {
unsafe { XRootWindow(self.ptr, screen_nr) } unsafe { XRootWindow(self.ptr, screen_nr) }
} }
pub fn get_keyboard_mapping(&self, keycode: Keycode, keycode_count: i32) -> &[Keysym] { pub fn get_keyboard_mapping(&self, keycode: Keycode, keycode_count: i32) -> &[Keysym] {
let mut keysyms_per_keycode = 0; let mut keysyms_per_keycode = 0;
let r = unsafe { let r = unsafe {
let ptr = XGetKeyboardMapping( let ptr = XGetKeyboardMapping(
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)
}; };
r r
} }
pub fn convert_case(&self, keysym: Keysym) -> (Keysym, Keysym) { pub fn convert_case(&self, keysym: Keysym) -> (Keysym, Keysym) {
let mut keysym_lower: Keysym = Keysym::default(); let mut keysym_lower: Keysym = Keysym::default();
let mut keysym_upper: Keysym = Keysym::default(); let mut keysym_upper: Keysym = Keysym::default();
unsafe { unsafe {
XConvertCase(keysym, &mut keysym_lower, &mut keysym_upper); XConvertCase(keysym, &mut keysym_lower, &mut keysym_upper);
} }
(keysym_lower, keysym_upper) (keysym_lower, keysym_upper)
} }
pub fn keysym_to_keycode(&self, keysym: Keysym) -> Keycode { pub fn keysym_to_keycode(&self, keysym: Keysym) -> Keycode {
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode } unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
} }
pub fn string_to_keycode(&self, string: &[u8]) -> Keycode { pub fn string_to_keycode(&self, string: &[u8]) -> Keycode {
self.keysym_to_keycode(string_to_keysym(string)) self.keysym_to_keycode(string_to_keysym(string))
} }
pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym { pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym {
unsafe { unsafe { XKeycodeToKeysym(self.ptr, keycode as c_uchar, 0) }
XKeycodeToKeysym(self.ptr, keycode as c_uchar, 0) }
}
}
pub fn keycode_to_string(&self, keycode: Keycode) -> CString { pub fn keycode_to_string(&self, keycode: Keycode) -> CString {
keysym_to_string(self.keycode_to_keysym(keycode)) keysym_to_string(self.keycode_to_keysym(keycode))
} }
// XTest stuff // XTest stuff
pub fn has_xtest(&self) -> bool { pub fn has_xtest(&self) -> bool {
let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0); let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0);
let has_extension = unsafe { let has_extension = unsafe {
XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3) XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3)
}; };
has_extension != 0 has_extension != 0
} }
pub fn send_fake_keypress_from_string(&self, string: &[u8], delay: u64) { pub fn send_fake_keypress_from_string(&self, string: &[u8], delay: u64) {
self.send_fake_keypress_from_keysym(string_to_keysym(string), delay) self.send_fake_keypress_from_keysym(string_to_keysym(string), delay)
} }
pub fn send_fake_keypress_from_keysym(&self, ks: Keysym, delay: u64) { pub fn send_fake_keypress_from_keysym(&self, ks: Keysym, delay: u64) {
self.send_fake_keypress_from_code(self.keysym_to_keycode(ks), delay) self.send_fake_keypress_from_code(self.keysym_to_keycode(ks), delay)
} }
pub fn send_fake_keypress_from_code(&self, code: Keycode, delay: u64) { pub fn send_fake_keypress_from_code(&self, code: Keycode, delay: u64) {
unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, delay) }; unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, delay) };
self.flush(); self.flush();
} }
pub fn send_fake_buttonpress(&self, button: u32, delay: u64) { pub fn send_fake_buttonpress(&self, button: u32, delay: u64) {
unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, delay) }; unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, delay) };
} }
pub fn send_fake_buttonrelease(&self, button: u32, delay: u64) { pub fn send_fake_buttonrelease(&self, button: u32, delay: u64) {
unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, delay) }; unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, delay) };
} }
pub fn send_fake_keyrelease_from_string(&self, string: &[u8], delay: u64) { pub fn send_fake_keyrelease_from_string(&self, string: &[u8], delay: u64) {
self.send_fake_keyrelease_from_keysym(string_to_keysym(string), delay) self.send_fake_keyrelease_from_keysym(string_to_keysym(string), delay)
} }
pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym, delay: u64) { pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym, delay: u64) {
self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks), delay) self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks), delay)
} }
pub fn send_fake_keyrelease_from_code(&self, code: Keycode, delay: u64) { pub fn send_fake_keyrelease_from_code(&self, code: Keycode, delay: u64) {
unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, delay) }; unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, delay) };
self.flush(); self.flush();
} }
pub fn send_fake_motion_event(&self, x: i32, y: i32, delay: u64) { pub fn send_fake_motion_event(&self, x: i32, y: i32, delay: u64) {
unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, delay) }; unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, delay) };
self.flush(); self.flush();
} }
pub fn grab_control(&self) { pub fn grab_control(&self) {
unsafe { unsafe {
XTestGrabControl(self.ptr, TRUE_C); XTestGrabControl(self.ptr, TRUE_C);
} }
} }
pub fn allow_events(&self, event_mode: i32, time: Time) { pub fn allow_events(&self, event_mode: i32, time: Time) {
unsafe { XAllowEvents(self.ptr, event_mode, 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 { pub fn grab_keyboard(
unsafe { &self,
XGrabKeyboard( window: u64,
self.ptr, owner_events: bool,
window, pointer_mode: i32,
c_int::from(owner_events), keyboard_mode: i32,
pointer_mode, time: Time,
keyboard_mode, ) -> i32 {
time, unsafe {
) XGrabKeyboard(
} self.ptr,
} window,
c_int::from(owner_events),
pointer_mode,
keyboard_mode,
time,
)
}
}
pub fn ungrab_keyboard(&self, time: Time) { pub fn ungrab_keyboard(&self, time: Time) {
unsafe { XUngrabKeyboard(self.ptr, time) }; unsafe { XUngrabKeyboard(self.ptr, time) };
} }
pub fn ungrab_pointer(&self, time: Time) { pub fn ungrab_pointer(&self, time: Time) {
unsafe { XUngrabPointer(self.ptr, time) }; unsafe { XUngrabPointer(self.ptr, time) };
} }
pub fn window_event(&self, window: Window, event_mask: i64) -> XEvent { 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 // maybe dirty hack to initialize the event var?? idk how else to do this
let mut r: XEvent = XEvent { type_: GenericEvent }; let mut r: XEvent = XEvent {
type_: GenericEvent,
};
unsafe { XWindowEvent(self.ptr, window, event_mask, &mut r); } unsafe {
XWindowEvent(self.ptr, window, event_mask, &mut r);
}
r r
} }
// XRecord stuff // XRecord stuff
pub fn has_xrecord(&self) -> bool { pub fn has_xrecord(&self) -> bool {
let mut xrecord_version: (c_int, c_int) = (0, 0); 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) }; let xrec_res = unsafe {
xrec_res == 0 XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1)
} };
xrec_res == 0
}
pub fn create_record_context(&self, mut protocol_ranges: *mut XRecordRange) -> XRecordContext { pub fn create_record_context(&self, mut protocol_ranges: *mut XRecordRange) -> XRecordContext {
unsafe { unsafe {
(*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 =
XRecordCreateContext( unsafe { XRecordCreateContext(self.ptr, 0, &mut clients, 1, &mut protocol_ranges, 1) };
self.ptr, ctx
0, }
&mut clients,
1,
&mut protocol_ranges,
1,
)
};
ctx
}
pub fn enable_context(&self, pub fn enable_context(
ctx: XRecordContext, &self,
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, ctx: XRecordContext,
closure: *mut c_char, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
) -> bool { closure: *mut c_char,
unsafe { ) -> bool {
XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0 unsafe { XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0 }
} }
}
pub fn enable_context_async(&self, pub fn enable_context_async(
ctx: XRecordContext, &self,
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, ctx: XRecordContext,
closure: *mut c_char, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
) -> bool { closure: *mut c_char,
unsafe { ) -> bool {
XRecordEnableContextAsync(self.ptr, ctx, cb, closure as *mut c_char) != 0 unsafe { 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!
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) }
} }
pub fn keysym_to_string(keysym: Keysym) -> CString { pub fn keysym_to_string(keysym: Keysym) -> CString {
unsafe { unsafe {
let cstr = CStr::from_ptr(XKeysymToString(keysym)); let cstr = CStr::from_ptr(XKeysymToString(keysym));
CString::from(cstr) CString::from(cstr)
} }
} }