This commit is contained in:
Schrottkatze 2022-10-22 23:17:28 +02:00
parent 7ecde7cd5a
commit 466e56af34
16 changed files with 57 additions and 1411 deletions

32
Cargo.lock generated
View file

@ -76,7 +76,7 @@ version = "0.2.0"
dependencies = [
"anyhow",
"clap",
"x11",
"xcb",
]
[[package]]
@ -116,6 +116,12 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.12.0"
@ -128,12 +134,6 @@ version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
[[package]]
name = "pkg-config"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -167,6 +167,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.18"
@ -252,11 +261,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "x11"
version = "2.19.1"
name = "xcb"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a"
checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9"
dependencies = [
"bitflags",
"libc",
"pkg-config",
"quick-xml",
]

View file

@ -7,5 +7,5 @@ edition = "2021"
[dependencies]
anyhow = "1.0.65"
x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
xcb = { version = "1.1.1", features = [ "xtest", "record" ]}
clap = { version = "3.2.4", features = ["derive"] }

View file

@ -1,174 +0,0 @@
use clap::Parser;
use easymacros::chartbl::CHARTBL;
use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay};
use std::ffi::CString;
use std::io::stdin;
use std::process::{exit, Command};
use std::time::Duration;
use std::{fs, thread};
use x11::keysym::XK_Shift_L;
/// Macro player module for easymacros. It's compatible with xmacro macros.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// The file that contains the macro to run.
#[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)]
input_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>,
/// Delay for events to be sent.
#[clap(short, long)]
delay: Option<u64>,
}
fn main() {
let args = Args::parse();
let display = get_remote(args.display);
let delay = args.delay.unwrap_or(10);
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");
for instruction in input_file_contents.lines() {
run_instruction(instruction, &display, delay);
}
} else {
println!("No input file specified, reading from stdin.");
let stdin = stdin();
loop {
// TODO: Unify with macro_writer using trait objects
let mut line = String::new();
stdin
.read_line(&mut line)
.expect("Couldn't read line from stdin");
// 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();
}
fn get_remote(display_name: Option<String>) -> XDisplay {
let display = XDisplay::open(display_name);
if !display.has_xtest() {
eprintln!("XTest not supported!");
display.close();
exit(1)
}
display.grab_control();
display.sync();
display
}
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
let instruction_split: Vec<&str> = instruction.split(' ').collect();
match instruction_split[0] {
"Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
"ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
"ButtonRelease" => {
dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay)
}
"MotionNotify" => dpy.send_fake_motion_event(
instruction_split[1].parse().unwrap(),
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" => {
let key: Keysym = instruction_split[1].parse().unwrap();
dpy.send_fake_keypress_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,
),
"KeyStrRelease" => dpy.send_fake_keyrelease_from_string(
CString::new(instruction_split[1]).unwrap().as_bytes(),
delay,
),
"KeyStr" => {
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);
}
"String" => {
for c in instruction["String".len() + 1..].chars() {
send_char(dpy, c, delay);
}
}
"ExecBlock" | "ExecNoBlock" => {
let mut command = Command::new(instruction_split[1]);
for arg in &instruction_split[2..] {
command.arg(arg);
}
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) {
// get keystring from character and turn it into a keysym
let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
let keycode = dpy.keysym_to_keycode(keysym);
if keycode == 0 {
eprintln!("No keycode found for character '{}'", c);
return;
}
let map_ks = dpy.get_keyboard_mapping(keycode, 1);
if map_ks[0] == 0 {
eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
return;
}
let (ks_lower, ks_upper) = dpy.convert_case(keysym);
// check if shift has to be pressed as well
let mut shift_needed = true;
if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
shift_needed = false;
}
if keysym == ks_lower && keysym != ks_upper {
shift_needed = false;
}
if shift_needed {
dpy.send_fake_keypress_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,198 +0,0 @@
extern crate core;
use std::ffi::c_void;
use std::os::raw::c_char;
use std::process::exit;
use std::ptr::addr_of;
use clap::Parser;
use x11::xlib::{
CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree,
XKeyEvent,
};
use x11::xrecord::{
XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData,
};
use easymacros::ev_callback_data::EvCallbackData;
use easymacros::macro_writer::MacroWriter;
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.
#[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() {
let args = Args::parse();
let display = XDisplay::open(args.display.clone());
let recorded_display = XDisplay::open(args.display.clone());
let stop_key = get_stop_key(display);
let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
event_loop(display, recorded_display, stop_key, writer, args.max_delay);
display.close();
}
fn get_stop_key(display: XDisplay) -> Keycode {
let screen = display.get_default_screen();
let root = display.get_root_window(screen);
let potential_err =
display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
if potential_err != GrabSuccess {
eprintln!("Couldn't grab keyboard!");
exit(1);
}
println!("Press the key you want to use to stop recording the macro.");
let stop_key;
loop {
display.allow_events(SyncPointer, CurrentTime);
let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
stop_key = ev.keycode;
break;
}
display.ungrab_keyboard(CurrentTime);
display.ungrab_pointer(CurrentTime);
stop_key
}
fn event_loop(
xdpy: XDisplay,
recdpy: XDisplay,
stop_key: Keycode,
mut writer: MacroWriter,
max_delay: Option<Time>,
) {
let protocol_ranges = unsafe { XRecordAllocRange() };
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
if pointer_pos != Position(-1, -1) {
writer.write(Instructions::MotionNotify(pointer_pos))
}
let ctx = recdpy.create_record_context(protocol_ranges);
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) {
panic!("Failed to enable record context")
}
while ev_cb_data.working {
recdpy.process_replies();
}
xdpy.disable_context(ctx);
xdpy.free_context(ctx);
unsafe { XFree(protocol_ranges as *mut c_void) };
}
unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRecordInterceptData) {
let data = &mut *(closure as *mut EvCallbackData);
let intercept_data = &mut *intercept_data;
if intercept_data.category == XRecordStartOfData {
println!("Got start of data!");
data.last_event = intercept_data.server_time;
XRecordFreeData(intercept_data);
return;
} else if intercept_data.category == XRecordEndOfData {
println!("Got end of data!");
XRecordFreeData(intercept_data);
return;
}
let ev_type = *(intercept_data.data as *const u8);
if data.pos.0 == 0 || data.pos.1 == -1 {
if ev_type == MOTIONNOTIFY_U8 {
data.update_pos(intercept_data);
data.write_pos();
} else {
println!(
"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 {
match ev_type {
MOTIONNOTIFY_U8 => {
data.update_pos(intercept_data);
if !data.moving {
data.moving = true;
}
}
KEYPRESS_U8 | KEYRELEASE_U8 => {
let kc: u8 = *((intercept_data.data as usize + 1) as *const u8);
let keyname = data.xdpy.keycode_to_string(kc as u32);
if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 {
data.working = false;
} else {
if ev_type == KEYPRESS_U8 {
data.no_keypress_yet = false;
}
data.maybe_write_delay(intercept_data.server_time);
if data.ptr_is_moving() {
data.write_pos();
}
data.writer.write(if ev_type == KEYPRESS_U8 {
Instructions::KeyStrPress(keyname)
} else {
Instructions::KeyStrRelease(keyname)
});
}
}
BUTTONPRESS_U8 | BUTTONRELEASE_U8 => {
let bc: u8 = *((intercept_data.data as usize + 1) as *const u8);
data.maybe_write_delay(intercept_data.server_time);
if data.ptr_is_moving() {
data.write_pos();
}
data.writer.write(if ev_type == BUTTONPRESS_U8 {
Instructions::ButtonPress(bc)
} else {
Instructions::ButtonRelease(bc)
});
}
_ => eprintln!("Unknown event type: {:?}", ev_type),
}
}
data.ev_nr += 2;
XRecordFreeData(intercept_data)
}

View file

@ -1,11 +1,6 @@
use core::time;
use std::thread;
use easymacros::xwrap::{display, key, screen};
use x11::xlib;
use clap::Parser;
use anyhow::Result;
use xcb::x;
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
#[derive(Parser, Debug)]
@ -25,39 +20,28 @@ struct Args {
ignore_delay_capturing: bool,
}
fn main() -> anyhow::Result<()> {
pub fn main() -> 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);
let (connection, screen_nr) = xcb::Connection::connect(args.display.as_deref())?;
Ok(())
}
fn get_stop_key(display: &mut display::Display) -> key::Key {
let root = display.default_root_window();
fn get_stop_key(connection: xcb::Connection, screen_nr: i32) -> Result<x::Keycode> {
let setup = connection.get_setup();
let screen = setup.roots().nth(screen_nr as usize).unwrap();
display
.grab_keyboard(
root,
false,
display::GrabMode::Sync,
display::GrabMode::Sync,
xlib::CurrentTime,
)
.expect("keyboard should be available to be grabbed");
let grab_cookie = connection.send_request(&x::GrabKeyboard {
owner_events: true, // get all pointer events specified by the following mask
grab_window: screen.root(), // grab the root window
time: x::CURRENT_TIME,
pointer_mode: x::GrabMode::Async, // process events as normal, do not require sync
keyboard_mode: x::GrabMode::Async,
});
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));
let grab_reply = connection.wait_for_reply(grab_cookie);
assert!(grab_reply.status() == x::GrabStatus::Success, "GrabKeyboard did not succeed");
todo!()
}

View file

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

View file

@ -3,47 +3,15 @@ extern crate core;
use std::ffi::CString;
use std::fmt::{Display, Formatter};
use x11::xlib::{ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify, Time};
use crate::x11_safe_wrapper::{Keycode, Keysym};
pub mod xwrap;
use xcb::x::{Keycode, Keysym, Timestamp};
pub mod chartbl;
pub mod ev_callback_data;
pub mod macro_writer;
pub mod x11_safe_wrapper;
pub const KEYPRESS_U8: u8 = KeyPress as u8;
pub const KEYRELEASE_U8: u8 = KeyRelease as u8;
pub const BUTTONPRESS_U8: u8 = ButtonPress as u8;
pub const BUTTONRELEASE_U8: u8 = ButtonRelease as u8;
pub const MOTIONNOTIFY_U8: u8 = MotionNotify as u8;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct Position<T>(pub T, pub T);
impl From<Position<i32>> for Position<i16> {
fn from(pos: Position<i32>) -> Self {
Self(pos.0 as i16, pos.1 as i16)
}
}
impl From<(i32, i32)> for Position<i32> {
fn from(v: (i32, i32)) -> Self {
Position(v.0, v.1)
}
}
impl From<(i32, i32)> for Position<i16> {
fn from(v: (i32, i32)) -> Position<i16> {
let p1: Position<i32> = Position::from(v);
Position::from(p1)
}
}
pub enum Instructions<'a> {
Delay(Time),
pub enum Instruction<'a> {
Delay(Timestamp),
ButtonPress(u8),
ButtonRelease(u8),
MotionNotify(Position<i16>),
@ -60,29 +28,29 @@ pub enum Instructions<'a> {
ExecNoBlock(&'a str),
}
impl Display for Instructions<'_> {
impl Display for Instruction<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Instructions::Delay(d) => format!("Delay {}", d),
Instructions::ButtonPress(b) => format!("ButtonPress {}", b),
Instructions::ButtonRelease(b) => format!("ButtonRelease {}", b),
Instructions::MotionNotify(pos) => format!("MotionNotify {} {}", pos.0, pos.1),
Instructions::KeyCodePress(kc) => format!("KeyCodePress {}", kc),
Instructions::KeyCodeRelease(kc) => format!("KeyCodeRelease {}", kc),
Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks),
Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
Instructions::KeySym(ks) => format!("KeySym {}", ks),
Instructions::KeyStrPress(kstr) =>
Instruction::Delay(d) => format!("Delay {}", d),
Instruction::ButtonPress(b) => format!("ButtonPress {}", b),
Instruction::ButtonRelease(b) => format!("ButtonRelease {}", b),
Instruction::MotionNotify(pos) => format!("MotionNotify {} {}", pos.0, pos.1),
Instruction::KeyCodePress(kc) => format!("KeyCodePress {}", kc),
Instruction::KeyCodeRelease(kc) => format!("KeyCodeRelease {}", kc),
Instruction::KeySymPress(ks) => format!("KeySymPress {}", ks),
Instruction::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
Instruction::KeySym(ks) => format!("KeySym {}", ks),
Instruction::KeyStrPress(kstr) =>
format!("KeyStrPress {}", kstr.to_str().unwrap()),
Instructions::KeyStrRelease(kstr) =>
Instruction::KeyStrRelease(kstr) =>
format!("KeyStrRelease {}", kstr.to_str().unwrap()),
Instructions::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()),
Instructions::String(str) => format!("String {}", str),
Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd),
Instructions::ExecNoBlock(cmd) => format!("ExecNoBlock {}", cmd),
Instruction::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()),
Instruction::String(str) => format!("String {}", str),
Instruction::ExecBlock(cmd) => format!("ExecBlock {}", cmd),
Instruction::ExecNoBlock(cmd) => format!("ExecNoBlock {}", cmd),
},
)
}

View file

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

View file

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

View file

@ -1,226 +0,0 @@
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);
};
}
}

View file

@ -1,62 +0,0 @@
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 {}

View file

@ -1,166 +0,0 @@
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 {}

View file

@ -1,39 +0,0 @@
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() })
})
}
}

View file

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

View file

@ -1,15 +0,0 @@
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) },
}
}
}

View file

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