Compare commits

..

No commits in common. "development" and "main" have entirely different histories.

18 changed files with 848 additions and 1498 deletions

7
Cargo.lock generated
View file

@ -2,12 +2,6 @@
# 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"
@ -74,7 +68,6 @@ dependencies = [
name = "easymacros" name = "easymacros"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"anyhow",
"clap", "clap",
"x11", "x11",
] ]

View file

@ -6,6 +6,5 @@ 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

@ -27,17 +27,18 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
- [ ] Rebrand? - [ ] Rebrand?
- [ ] new name - [ ] new name
- [ ] logo - [ ] logo
- [ ] Listening/remapping - [ ] macro language (easymacros daemon?)
- [ ] Modes - [ ] basic interpreter/compiler to fast intermediate lang
- [ ] Way to show current mode - [ ] stdlib
- [ ] mode change notifications? - [ ] xlib stuff (get window handles/ids)
- [ ] small gui/popups? - [ ] easy xtst/xrecord etc abstractions
- [ ] allow passing keys through in some modes - [ ] number/text inputs
- [ ] make modes listen for numbers/amounts of repetitions - [ ] clipboard
- [ ] make shortcuts be able to listen for other shortcuts and inputs - [ ] basic gui features
- [ ] rofi integration - [ ] filesystem stuff
- [ ] autorun stuff on some windows? - [ ] shell stuff
- [ ] Proper, safe xlib wrapper - [ ] find image/track image/wait for image...
- [ ] event listeners
# #

View file

@ -1,12 +1,12 @@
use clap::Parser; use clap::Parser;
use easymacros::chartbl::CHARTBL; use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay};
use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay};
use std::ffi::CString; use std::ffi::CString;
use std::io::stdin; use std::process::{Command, exit};
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)]
@ -30,8 +30,7 @@ fn main() {
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 = let input_file_contents = fs::read_to_string(input_file_path).expect("Couldn't read macro file");
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);
@ -41,11 +40,8 @@ fn main() {
let stdin = stdin(); let stdin = stdin();
loop { loop {
// TODO: Unify with macro_writer using trait objects
let mut line = String::new(); let mut line = String::new();
stdin stdin.read_line(&mut line).expect("Couldn't read line from 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? // Without this it crashes because apparently it doesn't properly read the next input line?
println!(); println!();
line = line.trim().to_string(); line = line.trim().to_string();
@ -77,40 +73,20 @@ fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
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" => { "ButtonRelease" => dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay),
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),
"MotionNotify" => dpy.send_fake_motion_event( "KeyCodeRelease" => dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay),
instruction_split[1].parse().unwrap(), "KeySymPress" => dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay),
instruction_split[2].parse().unwrap(), "KeySymRelease" => dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay),
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( "KeyStrPress" => dpy.send_fake_keypress_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
CString::new(instruction_split[1]).unwrap().as_bytes(), "KeyStrRelease" => dpy.send_fake_keyrelease_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
delay,
),
"KeyStrRelease" => dpy.send_fake_keyrelease_from_string(
CString::new(instruction_split[1]).unwrap().as_bytes(),
delay,
),
"KeyStr" => { "KeyStr" => {
let keystring = CString::new(instruction_split[1]).unwrap(); let keystring = CString::new(instruction_split[1]).unwrap();
dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay); dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
@ -163,12 +139,8 @@ fn send_char(dpy: &XDisplay, c: char, delay: u64) {
shift_needed = false; shift_needed = false;
} }
if shift_needed { if shift_needed { dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); }
dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay);
}
dpy.send_fake_keypress_from_code(keycode, delay); dpy.send_fake_keypress_from_code(keycode, delay);
dpy.send_fake_keyrelease_from_code(keycode, delay); dpy.send_fake_keyrelease_from_code(keycode, delay);
if shift_needed { if shift_needed { dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); }
dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay);
}
} }

View file

@ -1,26 +1,18 @@
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::{ use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, XKeyEvent};
CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, use x11::xrecord::{XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData};
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)]
@ -48,7 +40,13 @@ fn main() {
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(display, recorded_display, stop_key, writer, args.max_delay); event_loop(
display,
recorded_display,
stop_key,
writer,
args.max_delay,
);
display.close(); display.close();
} }
@ -57,8 +55,7 @@ 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 = let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
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!");
@ -97,8 +94,7 @@ fn event_loop(
} }
let ctx = recdpy.create_record_context(protocol_ranges); let ctx = recdpy.create_record_context(protocol_ranges);
let ev_cb_data = let ev_cb_data = EvCallbackData::new(writer, xdpy, recdpy, ctx, stop_key, pointer_pos, max_delay);
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")
@ -134,10 +130,8 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
data.update_pos(intercept_data); data.update_pos(intercept_data);
data.write_pos(); data.write_pos();
} else { } else {
println!( println!("Move your cursor so the macro can start with a fixed cursor position!
"Move your cursor so the macro can start with a fixed cursor position! Skipping event...");
Skipping event..."
);
} }
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 { } else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
println!("Skipping KeyRelease without recorded KeyPress..."); println!("Skipping KeyRelease without recorded KeyPress...");
@ -163,9 +157,7 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
data.maybe_write_delay(intercept_data.server_time); data.maybe_write_delay(intercept_data.server_time);
if data.ptr_is_moving() { if data.ptr_is_moving() { data.write_pos(); }
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)
@ -179,9 +171,7 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
data.maybe_write_delay(intercept_data.server_time); data.maybe_write_delay(intercept_data.server_time);
if data.ptr_is_moving() { if data.ptr_is_moving() { data.write_pos(); }
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)
@ -189,10 +179,11 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
Instructions::ButtonRelease(bc) Instructions::ButtonRelease(bc)
}); });
} }
_ => eprintln!("Unknown event type: {:?}", ev_type), _ => eprintln!("Unknown event type: {:?}", ev_type)
} }
} }
data.ev_nr += 2; data.ev_nr += 2;
XRecordFreeData(intercept_data) XRecordFreeData(intercept_data)
} }

View file

@ -1,63 +0,0 @@
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,10 +1,10 @@
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 {
@ -13,11 +13,11 @@ pub struct EvCallbackData {
pub recdpy: XDisplay, pub recdpy: XDisplay,
pub ctx: XRecordContext, pub ctx: XRecordContext,
pub working: bool, pub working: bool,
pub last_event: xlib::Time, pub last_event: Time,
pub pos: Position<i16>, pub pos: Position<i16>,
pub stop_key: Keycode, pub stop_key: Keycode,
pub ev_nr: u32, pub ev_nr: u32,
pub max_delay: Option<xlib::Time>, pub max_delay: Option<Time>,
pub no_keypress_yet: bool, pub no_keypress_yet: bool,
pub moving: bool, pub moving: bool,
} }
@ -30,7 +30,7 @@ impl EvCallbackData {
ctx: XRecordContext, ctx: XRecordContext,
stop_key: Keycode, stop_key: Keycode,
pos: Position<i16>, pos: Position<i16>,
max_delay: Option<xlib::Time>, max_delay: Option<Time>,
) -> Self { ) -> Self {
EvCallbackData { EvCallbackData {
writer, writer,
@ -43,24 +43,16 @@ impl EvCallbackData {
pos, pos,
max_delay, max_delay,
no_keypress_yet: true, no_keypress_yet: true,
last_event: time::SystemTime::now() last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
.duration_since(time::UNIX_EPOCH)
.unwrap()
.as_millis() as xlib::Time,
moving: false, moving: false,
} }
} }
pub fn ptr_is_moving(&self) -> bool { pub fn ptr_is_moving(&self) -> bool { self.moving }
self.moving
}
pub unsafe fn update_pos( pub unsafe fn update_pos(&mut self, intercept_data: &mut XRecordInterceptData) -> Position<i16> {
&mut self, self.pos.0 = *((intercept_data.data as usize + size_of::<i16>() * 10) as *const i16);
intercept_data: &mut XRecordInterceptData, self.pos.1 = *((intercept_data.data as usize + size_of::<i16>() * 11) as *const i16);
) -> 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 self.pos
} }
@ -69,23 +61,15 @@ impl EvCallbackData {
self.moving = false; self.moving = false;
} }
pub fn maybe_write_delay(&mut self, server_time: xlib::Time) { pub fn maybe_write_delay(&mut self, server_time: Time) {
if server_time - self.last_event > 1 { if server_time - self.last_event > 1 {
self.writer.write(Instructions::Delay(calculate_delay( self.writer.write(Instructions::Delay(calculate_delay(server_time, self.last_event, self.max_delay)));
server_time,
self.last_event,
self.max_delay,
)));
self.last_event = server_time; self.last_event = server_time;
} }
} }
} }
fn calculate_delay( fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>) -> Time {
server_time: xlib::Time,
last_event: xlib::Time,
max_delay: Option<xlib::Time>,
) -> xlib::Time {
if let Some(max) = max_delay { if let Some(max) = max_delay {
let max = max as u64; let max = max as u64;
let delay = server_time - last_event; let delay = server_time - last_event;
@ -99,3 +83,4 @@ fn calculate_delay(
server_time - last_event server_time - last_event
} }
} }

View file

@ -7,12 +7,10 @@ use x11::xlib::{ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify,
use crate::x11_safe_wrapper::{Keycode, Keysym}; use crate::x11_safe_wrapper::{Keycode, Keysym};
pub mod xwrap;
pub mod chartbl;
pub mod ev_callback_data;
pub mod macro_writer;
pub mod x11_safe_wrapper; pub mod x11_safe_wrapper;
pub mod chartbl;
pub mod macro_writer;
pub mod ev_callback_data;
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;
@ -21,11 +19,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)
} }
} }
@ -62,9 +60,7 @@ 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!( write!(f, "{}",
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),
@ -75,10 +71,8 @@ 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) => Instructions::KeyStrPress(kstr) => format!("KeyStrPress {}", kstr.to_str().unwrap()),
format!("KeyStrPress {}", kstr.to_str().unwrap()), Instructions::KeyStrRelease(kstr) => format!("KeyStrRelease {}", 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,8 +1,10 @@
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 io::Write>, outfile: Box<dyn Write>,
ignore_delay_capturing: bool, ignore_delay_capturing: bool,
} }
@ -10,7 +12,7 @@ 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(fs::File::create(outfile).expect("Failed to create output file")) Box::new(File::create(outfile).expect("Failed to create output file"))
} else { } else {
Box::new(io::stdout()) Box::new(io::stdout())
}, },
@ -20,10 +22,12 @@ impl MacroWriter {
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) writeln!(&mut self.outfile, "{}", instruction).expect("Failed to write instruction to outfile");
.expect("Failed to write instruction to outfile");
} }
} }

View file

@ -1,18 +1,9 @@
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::{ 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};
Display, GenericEvent, KeyPress, MotionNotify, Time, Window, XAllowEvents, XCloseDisplay, use x11::xrecord::{XRecordAllClients, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange};
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,
@ -35,8 +26,7 @@ impl XDisplay {
name name
} else { } else {
env::var("DISPLAY").expect("DISPLAY is not set") env::var("DISPLAY").expect("DISPLAY is not set")
}) }).unwrap();
.unwrap();
let name_ptr = name.as_bytes().as_ptr(); let name_ptr = name.as_bytes().as_ptr();
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) }; let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
@ -125,7 +115,9 @@ impl XDisplay {
} }
pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym { pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym {
unsafe { XKeycodeToKeysym(self.ptr, keycode as c_uchar, 0) } unsafe {
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 {
@ -190,14 +182,7 @@ impl XDisplay {
unsafe { XAllowEvents(self.ptr, event_mode, time) }; unsafe { XAllowEvents(self.ptr, event_mode, time) };
} }
pub fn grab_keyboard( pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 {
&self,
window: u64,
owner_events: bool,
pointer_mode: i32,
keyboard_mode: i32,
time: Time,
) -> i32 {
unsafe { unsafe {
XGrabKeyboard( XGrabKeyboard(
self.ptr, self.ptr,
@ -219,13 +204,9 @@ impl XDisplay {
} }
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 { let mut r: XEvent = XEvent { type_: GenericEvent };
type_: GenericEvent,
};
unsafe { unsafe { XWindowEvent(self.ptr, window, event_mask, &mut r); }
XWindowEvent(self.ptr, window, event_mask, &mut r);
}
r r
} }
@ -233,9 +214,7 @@ impl XDisplay {
// 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 { let xrec_res = unsafe { XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1) };
XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1)
};
xrec_res == 0 xrec_res == 0
} }
@ -246,35 +225,49 @@ impl XDisplay {
} }
let mut clients: XRecordClientSpec = XRecordAllClients; let mut clients: XRecordClientSpec = XRecordAllClients;
let ctx: XRecordContext = let ctx: XRecordContext = unsafe {
unsafe { XRecordCreateContext(self.ptr, 0, &mut clients, 1, &mut protocol_ranges, 1) }; XRecordCreateContext(
self.ptr,
0,
&mut clients,
1,
&mut protocol_ranges,
1,
)
};
ctx ctx
} }
pub fn enable_context( pub fn enable_context(&self,
&self,
ctx: XRecordContext, ctx: XRecordContext,
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
closure: *mut c_char, closure: *mut c_char,
) -> bool { ) -> bool {
unsafe { 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( pub fn enable_context_async(&self,
&self,
ctx: XRecordContext, ctx: XRecordContext,
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>, cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
closure: *mut c_char, closure: *mut c_char,
) -> bool { ) -> bool {
unsafe { 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 { XRecordDisableContext(self.ptr, ctx) != 0 } unsafe {
XRecordDisableContext(self.ptr, ctx) != 0
}
} }
pub fn free_context(&self, ctx: XRecordContext) -> bool { pub fn free_context(&self, ctx: XRecordContext) -> bool {
unsafe { XRecordFreeContext(self.ptr, ctx) != 0 } unsafe {
XRecordFreeContext(self.ptr, ctx) != 0
}
} }
pub fn process_replies(&self) { pub fn process_replies(&self) {

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,
}