Compare commits

...

8 commits

Author SHA1 Message Date
7ecde7cd5a woops i forgot stuff in last commit 2022-10-22 20:48:14 +02:00
8dd78c66b7 i genuinely have no idea what i changed. i probably added stuff. 2022-10-22 20:47:53 +02:00
52a61e070d stuffies 2022-10-11 03:07:22 +02:00
1b688c7883 progress! 2022-10-11 02:36:39 +02:00
c4ebaa35be did stuff 2022-10-10 20:36:25 +02:00
7748132fca rewriting X wrapper 2022-10-10 20:03:18 +02:00
801f8b6265 cargo fmt 2022-10-10 20:03:00 +02:00
16c0826d3a updated TODOs and the projects aim 2022-10-10 09:41:56 +02:00
18 changed files with 1497 additions and 847 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

@ -10,35 +10,34 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
<summary><h2>:pen_ballpoint: TODOs :notepad_spiral:</h2></summary> <summary><h2>:pen_ballpoint: TODOs :notepad_spiral:</h2></summary>
- [x] Playing macros (xmacro like) - [x] Playing macros (xmacro like)
- [x] Delay support - [x] Delay support
- [x] KeySym/KeyCode/KeyStr action support - [x] KeySym/KeyCode/KeyStr action support
- [x] MotionNotify and button support - [x] MotionNotify and button support
- [x] String typing support (Not too high priority, but I'll add it some time probably) - [x] String typing support (Not too high priority, but I'll add it some time probably)
- [x] ExecBlock/ExecNoBlock support (not high priority) - [x] ExecBlock/ExecNoBlock support (not high priority)
- [x] ExecBlock - [x] ExecBlock
- [x] ExecNoBlock - [x] ExecNoBlock
- [x] Recording macros (xmacro like) - [x] Recording macros (xmacro like)
- [x] Delay - [x] Delay
- [x] Keyboard actions - [x] Keyboard actions
- [x] Mouse actions - [x] Mouse actions
- [x] Utilities for playing macros - [x] Utilities for playing macros
- [ ] Ignoring delays when playing - [ ] Ignoring delays when playing
- [x] Event delay support - [x] Event delay support
- [ ] Rebrand? - [ ] Rebrand?
- [ ] new name - [ ] new name
- [ ] logo - [ ] logo
- [ ] macro language (easymacros daemon?) - [ ] Listening/remapping
- [ ] basic interpreter/compiler to fast intermediate lang - [ ] Modes
- [ ] stdlib - [ ] Way to show current mode
- [ ] xlib stuff (get window handles/ids) - [ ] mode change notifications?
- [ ] easy xtst/xrecord etc abstractions - [ ] small gui/popups?
- [ ] number/text inputs - [ ] allow passing keys through in some modes
- [ ] clipboard - [ ] make modes listen for numbers/amounts of repetitions
- [ ] basic gui features - [ ] make shortcuts be able to listen for other shortcuts and inputs
- [ ] filesystem stuff - [ ] rofi integration
- [ ] shell stuff - [ ] autorun stuff on some windows?
- [ ] find image/track image/wait for image... - [ ] Proper, safe xlib wrapper
- [ ] event listeners
# #

View file

@ -1,146 +1,174 @@
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(); // TODO: Unify with macro_writer using trait objects
stdin.read_line(&mut line).expect("Couldn't read line from stdin"); let mut line = String::new();
// Without this it crashes because apparently it doesn't properly read the next input line? stdin
println!(); .read_line(&mut line)
line = line.trim().to_string(); .expect("Couldn't read line from stdin");
run_instruction(&*line, &display, delay); // Without this it crashes because apparently it doesn't properly read the next input line?
} 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)
} }

63
src/bin/emrec.rs Normal file
View file

@ -0,0 +1,63 @@
use core::time;
use std::thread;
use easymacros::xwrap::{display, key, screen};
use x11::xlib;
use clap::Parser;
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// The file to record the macro to. Defaults to writing to stdout.
#[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)]
output_file: Option<std::path::PathBuf>,
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
#[clap(short = 'D', long)]
display: Option<String>,
/// Max Delay in milliseconds for macro delays
#[clap(short, long)]
max_delay: Option<u64>,
/// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay.
#[clap(short, long)]
ignore_delay_capturing: bool,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let mut display =
display::Display::open(args.display.clone()).expect("should be able to open to display");
let stop_key = get_stop_key(&mut display);
dbg!(stop_key);
Ok(())
}
fn get_stop_key(display: &mut display::Display) -> key::Key {
let root = display.default_root_window();
display
.grab_keyboard(
root,
false,
display::GrabMode::Sync,
display::GrabMode::Sync,
xlib::CurrentTime,
)
.expect("keyboard should be available to be grabbed");
println!("Press the key you want to use to stop recording the macro.");
let stop_key = loop {
display.allow_events(display::EventMode::SyncPointer, xlib::CurrentTime);
};
thread::sleep(time::Duration::from_secs(3));
todo!()
}

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,101 @@
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::macro_writer::MacroWriter;
use crate::x11_safe_wrapper::XDisplay; use crate::x11_safe_wrapper::XDisplay;
use crate::{Instructions, Keycode, Position};
use std::mem;
use std::time;
use x11::xlib;
use x11::xrecord::{XRecordContext, XRecordInterceptData};
#[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: xlib::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<xlib::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<xlib::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: time::SystemTime::now()
moving: false, .duration_since(time::UNIX_EPOCH)
} .unwrap()
} .as_millis() as xlib::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 + mem::size_of::<i16>() * 10) as *const i16);
self.pos.1 = *((intercept_data.data as usize + mem::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: xlib::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(
if let Some(max) = max_delay { server_time: xlib::Time,
let max = max as u64; last_event: xlib::Time,
let delay = server_time - last_event; max_delay: Option<xlib::Time>,
) -> xlib::Time {
if let Some(max) = max_delay {
let max = max as u64;
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,29 @@
use std::fs::File;
use std::io;
use std::io::Write;
use crate::Instructions; use crate::Instructions;
use std::{fs, io};
pub struct MacroWriter { pub struct MacroWriter {
outfile: Box<dyn Write>, outfile: Box<dyn io::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(fs::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; }
}
}
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)
} }
} }

226
src/xwrap/display.rs Normal file
View file

@ -0,0 +1,226 @@
use std::{env, ffi, ops, ptr};
use x11::xlib::{self, BadGC};
use anyhow::Result;
use super::{error, screen, window};
pub struct Display {
pub(super) ptr: *mut xlib::Display,
name: String,
keyboard_grab: Option<GrabbablesModes>,
pointer_grab: Option<GrabbablesModes>,
}
// for keyboard/pointer grabs so Display is less messy
#[derive(Debug)]
struct GrabbablesModes {
keyboard_mode: GrabMode,
pointer_mode: GrabMode,
}
#[derive(Debug)]
pub enum Grabbables {
Keyboard,
Pointer,
}
impl std::fmt::Display for Grabbables {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Grabbables::Keyboard => write!(f, "keyboard"),
Grabbables::Pointer => write!(f, "pointer"),
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum GrabMode {
Sync,
Async,
}
impl From<GrabMode> for i32 {
fn from(v: GrabMode) -> Self {
match v {
GrabMode::Sync => xlib::GrabModeSync,
GrabMode::Async => xlib::GrabModeAsync,
}
}
}
/// The event mode for XAllowEvents
pub enum EventMode {
AsyncPointer,
SyncPointer,
AsyncKeyboard,
SyncKeyboard,
ReplayPointer,
ReplayKeyboard,
AsyncBoth,
SyncBoth
}
impl From<EventMode> for i32 {
fn from(event_mode: EventMode) -> Self {
match event_mode {
EventMode::AsyncPointer => xlib::AsyncPointer,
EventMode::SyncPointer => xlib::SyncPointer,
EventMode::AsyncKeyboard => xlib::AsyncKeyboard,
EventMode::SyncKeyboard => xlib::SyncKeyboard,
EventMode::ReplayPointer => xlib::ReplayPointer,
EventMode::ReplayKeyboard => xlib::ReplayKeyboard,
EventMode::AsyncBoth => xlib::AsyncBoth,
EventMode::SyncBoth => xlib::SyncBoth,
}
}
}
impl Display {
/// Call XOpenDisplay to open a connection to the X Server.
/// If `display_name` is `None`, the value of the `DISPLAY` environment variable will be used.
pub fn open(display_name: Option<String>) -> Result<Self> {
let name = ffi::CString::new(if let Some(name) = display_name {
name
} else {
env::var("DISPLAY")?
})?;
let name_ptr = name.as_bytes().as_ptr();
// try to open display and get either display pointer or null
let display_ptr = unsafe { xlib::XOpenDisplay(name_ptr as *const i8) };
// if display is null, return an error, otherwise return instance successfully
if display_ptr == ptr::null_mut::<xlib::_XDisplay>() {
Err(error::XError::OpenDisplayError(name.into_string()?).into())
} else {
Ok(Self {
ptr: display_ptr,
name: name.into_string()?,
pointer_grab: None,
keyboard_grab: None,
})
}
}
/// Calls XFlush to flush the output buffer.
pub fn flush(&self) {
unsafe {
xlib::XFlush(self.ptr);
}
}
// TODO: Figure out how to properly handle errors
/// Calls XSync to flush the output buffer and then wait until all events have been received and processed
/// by the server.
/// The `discard` parameter specifies, whether to discard all events in the queue.
pub fn sync(&self, discard: bool) {
unsafe {
xlib::XSync(self.ptr, discard.into());
}
}
/// Calls xlib::XDefaultScreen to get the default screen number referenced by Display::open.
/// This should be used to retrieve the screen number in applications that'll only use a single
/// screen.
pub fn default_screen_nr(&self) -> i32 {
unsafe { xlib::XDefaultScreen(self.ptr) }
}
/// Gets the default screen
pub fn default_screen(&self) -> screen::Screen {
screen::Screen {
ptr: unsafe { xlib::XDefaultScreenOfDisplay(self.ptr) },
}
}
/// Gets the root window of the default screen
pub fn default_root_window(&self) -> window::Window {
window::Window {
wid: unsafe { xlib::XDefaultRootWindow(self.ptr) },
}
}
/// Get the range of legal KeyCodes for a display.
pub fn keycodes(&self) -> Result<ops::Range<i32>> {
let (mut min, mut max) = (0, 0);
if unsafe { xlib::XDisplayKeycodes(self.ptr, &mut min, &mut max) } == 0 {
Err(error::XError::DisplayKeycodesError.into())
} else {
Ok(min..max)
}
}
/// Performs an active Grab on the keyboard. Further key events are only reported to the
/// grabbing client.
pub fn grab_keyboard(
&mut self,
grab_window: window::Window,
owner_events: bool,
pointer_mode: GrabMode,
keyboard_mode: GrabMode,
time: xlib::Time,
) -> Result<()> {
if let None = self.keyboard_grab {
match unsafe {
xlib::XGrabKeyboard(
self.ptr,
grab_window.wid,
owner_events.into(),
pointer_mode.into(),
keyboard_mode.into(),
time,
)
} {
xlib::GrabSuccess => {
self.keyboard_grab = Some(GrabbablesModes {
keyboard_mode,
pointer_mode,
});
Ok(())
}
xlib::AlreadyGrabbed =>
Err(error::XError::XAlreadyGrabbed(Grabbables::Keyboard).into()),
xlib::GrabInvalidTime =>
Err(error::XError::XGrabInvalidTime.into()),
xlib::GrabNotViewable =>
Err(error::XError::XGrabNotViewable.into()),
xlib::GrabFrozen =>
Err(error::XError::XGrabFrozen(Grabbables::Keyboard).into()),
code => Err(error::XError::UnknownError(code).into()),
}
} else {
Err(error::XError::AlreadyGrabbed(Grabbables::Keyboard).into())
}
}
/// Ends the active keyboard grab.
pub fn ungrab_keyboard(&self, time: xlib::Time) -> Result<()>{
if let Some(_) = self.keyboard_grab {
unsafe {
xlib::XUngrabKeyboard(self.ptr, time);
Ok(())
}
} else {
Err(error::XError::NotGrabbed(Grabbables::Keyboard).into())
}
}
pub fn allow_events(&self, event_mode: EventMode, time: xlib::Time) {
unsafe {
xlib::XAllowEvents(self.ptr, event_mode.into(), time);
}
}
}
impl Drop for Display {
fn drop(&mut self) {
if unsafe { xlib::XCloseDisplay(self.ptr) } == BadGC.into() {
eprintln!("BadGC Error when closing display '{}'.", self.name);
};
}
}

62
src/xwrap/error.rs Normal file
View file

@ -0,0 +1,62 @@
use std::{fmt, ops};
use x11::xlib;
use super::display;
/// Various errors to be used in this wrapper
#[derive(Debug)]
pub enum XError {
OpenDisplayError(String),
DisplayKeycodesError,
InvalidKeycodeError(xlib::KeyCode, ops::Range<i32>),
AlreadyGrabbed(display::Grabbables),
NotGrabbed(display::Grabbables),
XAlreadyGrabbed(display::Grabbables),
XGrabFrozen(display::Grabbables),
XGrabInvalidTime,
XGrabNotViewable,
XEventConversionError { event_type: i32 },
UnknownError(i32),
}
impl std::fmt::Display for XError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
XError::OpenDisplayError(display_name) =>
format!("error when opening display '{}'", display_name),
XError::DisplayKeycodesError => String::from("error when running XDisplayKeycodes"),
XError::InvalidKeycodeError(code, range) =>
format!("keycode {} outside of range {:?}", code, range),
XError::AlreadyGrabbed(thing_attempted_to_grab) => format!(
"this display already grabbed the {}",
thing_attempted_to_grab
),
XError::NotGrabbed(thing_attempted_to_ungrab) => format!(
"couldn't ungrab the {} because it wasn't grabbed",
thing_attempted_to_ungrab
),
XError::XAlreadyGrabbed(thing_attempted_to_grab) => format!(
"{} is already actively grabbed by another client",
thing_attempted_to_grab
),
XError::XGrabFrozen(thing_attempted_to_grab) => format!(
"{} is frozen by an active grab of another client",
thing_attempted_to_grab
),
XError::XGrabInvalidTime => String::from("invalid grab time"),
XError::XGrabNotViewable => String::from("grab_window is not viewable"),
XError::XEventConversionError { event_type } => format!(
"invalid event type: {}",
event_type
),
XError::UnknownError(code) => format!("unknown error code was returned: {}", code),
}
)
}
}
impl std::error::Error for XError {}

166
src/xwrap/event.rs Normal file
View file

@ -0,0 +1,166 @@
use x11::xlib::{self, XKeyEvent};
use anyhow::Result;
use super::{display, window, key, error};
pub type Vec2<T> = (T, T);
pub enum Event {
// only for now relevant event types
KeyPressEvent(KeyEvent),
KeyReleaseEvent(KeyEvent),
ButtonPressEvent(ButtonEvent),
ButtonReleaseEvent(ButtonEvent),
MotionEvent(MotionEvent),
ErrorEvent(ErrorEvent),
CrossingEvent(CrossingEvent),
FocusChangeEvent(FocusChangeEvent),
ExposeEvent(ExposeEvent),
GraphicsExposeEvent(GraphicsExposeEvent),
NoExposeEvent(NoExposeEvent),
VisibilityEvent(VisibilityEvent),
CreateWindowEvent(CreateWindowEvent),
DestroyWindowEvent(DestroyWindowEvent),
UnmapEvent(UnmapEvent),
MapEvent(MapEvent),
MapRequestEvent(MapRequestEvent),
ReparentEvent(ReparentEvent),
ConfigureEvent(ConfigureEvent),
GravityEvent(GravityEvent),
ResizeRequestEvent(ResizeRequestEvent),
ConfigureRequestEvent(ConfigureRequestEvent),
CirculateEvent(CirculateEvent),
CirculateRequestEvent(CirculateRequestEvent),
PropertyEvent(PropertyEvent),
SelectionClearEvent(SelectionClearEvent),
SelectionRequestEvent(SelectionRequestEvent),
SelectionEvent(SelectionEvent),
ColormapEvent(ColormapEvent),
ClientMessageEvent(ClientMessageEvent),
MappingEvent(MappingEvent),
KeymapEvent(KeymapEvent),
}
impl TryFrom<xlib::XEvent> for Event {
type Error = error::XError;
fn try_from(ev_union: xlib::XEvent) -> Result<Self, Self::Error> {
match ev_union.get_type() {
xlib::KeyPress => ,
xlib::KeyRelease => ,
xlib::ButtonPress => ,
xlib::ButtonRelease => ,
xlib::MotionNotify => ,
}
}
}
struct GenericEventData {
serial_nr: u64,
sent_by_different_client: bool,
source_display_ptr: *mut xlib::_XDisplay,
window: window::Window,
}
pub enum SwitchState {
Pressed,
Released
}
pub struct KeyEvent {
generic: GenericEventData,
root: window::Window,
subwindow: window::Window,
time: xlib::Time,
pointer_pos: Vec2<i32>,
pointer_pos_root: Vec2<i32>,
state: SwitchState,
key: key::Key,
same_screen: bool
}
impl TryFrom<xlib::XEvent> for KeyEvent {
type Error = error::XError;
fn try_from(raw_ev: xlib::XEvent) -> Result<Self, Self::Error> {
let state = match raw_ev.get_type() {
xlib::KeyPress => SwitchState::Pressed,
xlib::KeyRelease => SwitchState::Released,
ev_type => return Err(error::XError::XEventConversionError { event_type: ev_type })
};
let raw_ev = XKeyEvent::from(raw_ev);
Ok(Self {
generic: GenericEventData {
serial_nr: raw_ev.serial,
sent_by_different_client: raw_ev.send_event != 0,
// make conversion method for display from ptr that maybe should try to check???
// how would i even do this safely
source_display_ptr: raw_ev.display,
window: window::Window { wid: raw_ev.window },
},
root: window::Window { wid: raw_ev.root },
subwindow: window::Window { wid: raw_ev.subwindow } ,
time: raw_ev.time,
pointer_pos: (raw_ev.x, raw_ev.y),
pointer_pos_root: (raw_ev.x_root, raw_ev.y_root),
state: raw_ev.state,
key: key::Key { code: raw_ev.keycode },
same_screen: raw_ev.same_screen != 0
})
}
}
pub struct ButtonEvent {
generic: GenericEventData,
root: window::Window,
time: xlib::Time,
pointer_pos: Vec2<i32>,
pointer_pos_root: Vec2<i32>,
button: u32,
state: SwitchState,
same_screen: bool
}
// change fields and data for this and other events according to xlib doc p. 188-189
pub struct MotionEvent {
generic: GenericEventData,
root: window::Window,
subwindow: window::Window,
time: xlib::Time,
pointer_pos: Vec2<i32>,
pointer_pos_root: Vec2<i32>,
state: u32,
is_hint: u32,
same_screen: bool,
}
// TODO: make these into cool event stuff too
pub struct ErrorEvent {}
pub struct CrossingEvent {}
pub struct FocusChangeEvent {}
pub struct ExposeEvent {}
pub struct GraphicsExposeEvent {}
pub struct NoExposeEvent {}
pub struct VisibilityEvent {}
pub struct CreateWindowEvent {}
pub struct DestroyWindowEvent {}
pub struct UnmapEvent {}
pub struct MapEvent {}
pub struct MapRequestEvent {}
pub struct ReparentEvent {}
pub struct ConfigureEvent {}
pub struct GravityEvent {}
pub struct ResizeRequestEvent {}
pub struct ConfigureRequestEvent {}
pub struct CirculateEvent {}
pub struct CirculateRequestEvent {}
pub struct PropertyEvent {}
pub struct SelectionClearEvent {}
pub struct SelectionRequestEvent {}
pub struct SelectionEvent {}
pub struct ColormapEvent {}
pub struct ClientMessageEvent {}
pub struct MappingEvent {}
pub struct KeymapEvent {}

39
src/xwrap/key.rs Normal file
View file

@ -0,0 +1,39 @@
use x11::xlib;
use super::{display, error};
use anyhow::Result;
pub use x11::keysym;
/// Stores a keycode
#[derive(Debug)]
pub struct Key {
code: u32,
}
impl Key {
/// Creates a `Key` from a keycode and its related display connection.
pub fn from_code(code: xlib::KeyCode, display: &display::Display) -> Result<Self> {
let valid_range = display.keycodes()?;
if valid_range.contains(&code.into()) {
Ok(Key { code: code.into() })
} else {
Err(error::XError::InvalidKeycodeError(code, valid_range).into())
}
}
/// Creates a `Key` from a keysym by converting it into a code internally.
pub fn from_keysym(keysym: xlib::KeySym, display: &display::Display) -> Result<Option<Self>> {
let valid_range = display.keycodes()?;
let code = unsafe { xlib::XKeysymToKeycode(display.ptr, keysym) };
Ok(if code == 0 {
None
} else {
Some(Key { code: code.into() })
})
}
}

6
src/xwrap/mod.rs Normal file
View file

@ -0,0 +1,6 @@
pub mod display;
pub mod error;
pub mod key;
pub mod screen;
pub mod window;
pub mod event;

15
src/xwrap/screen.rs Normal file
View file

@ -0,0 +1,15 @@
use x11::xlib;
use super::display;
pub struct Screen {
pub(super) ptr: *mut xlib::Screen,
}
impl Screen {
pub fn new(screen_nr: i32, display: &display::Display) -> Self {
Self {
ptr: unsafe { xlib::XScreenOfDisplay(display.ptr, screen_nr) },
}
}
}

5
src/xwrap/window.rs Normal file
View file

@ -0,0 +1,5 @@
use x11::xlib;
pub struct Window {
pub(super) wid: xlib::Window,
}