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.
version = 3
[[package]]
name = "anyhow"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "atty"
version = "0.2.14"
@ -68,6 +74,7 @@ dependencies = [
name = "easymacros"
version = "0.2.0"
dependencies = [
"anyhow",
"clap",
"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
[dependencies]
anyhow = "1.0.65"
x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
clap = { version = "3.2.4", features = ["derive"] }

View file

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

View file

@ -1,12 +1,12 @@
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::process::{Command, exit};
use std::io::stdin;
use std::process::{exit, Command};
use std::time::Duration;
use std::{fs, thread};
use std::io::stdin;
use x11::keysym::XK_Shift_L;
use easymacros::chartbl::CHARTBL;
/// Macro player module for easymacros. It's compatible with xmacro macros.
#[derive(Parser, Debug)]
@ -30,7 +30,8 @@ fn main() {
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");
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);
@ -40,8 +41,11 @@ fn main() {
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");
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();
@ -73,20 +77,40 @@ fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
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),
"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),
"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);
@ -139,8 +163,12 @@ fn send_char(dpy: &XDisplay, c: char, delay: u64) {
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_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); }
if shift_needed {
dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay);
}
}

View file

@ -1,18 +1,26 @@
extern crate core;
use std::ffi::c_void;
use std::os::raw::{c_char};
use std::process::{exit};
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 x11::xlib::{
CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree,
XKeyEvent,
};
use x11::xrecord::{
XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData,
};
use easymacros::{BUTTONPRESS_U8, BUTTONRELEASE_U8, Instructions, KEYPRESS_U8, KEYRELEASE_U8, MOTIONNOTIFY_U8, Position};
use easymacros::ev_callback_data::EvCallbackData;
use easymacros::macro_writer::MacroWriter;
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
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)]
@ -40,13 +48,7 @@ fn main() {
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,
);
event_loop(display, recorded_display, stop_key, writer, args.max_delay);
display.close();
}
@ -55,7 +57,8 @@ 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);
let potential_err =
display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
if potential_err != GrabSuccess {
eprintln!("Couldn't grab keyboard!");
@ -94,7 +97,8 @@ fn event_loop(
}
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) {
panic!("Failed to enable record context")
@ -130,8 +134,10 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
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...");
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...");
@ -157,7 +163,9 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
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 {
Instructions::KeyStrPress(keyname)
@ -171,7 +179,9 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
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 {
Instructions::ButtonPress(bc)
@ -179,11 +189,10 @@ unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRec
Instructions::ButtonRelease(bc)
});
}
_ => eprintln!("Unknown event type: {:?}", ev_type)
_ => eprintln!("Unknown event type: {:?}", ev_type),
}
}
data.ev_nr += 2;
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,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::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 {
@ -13,11 +13,11 @@ pub struct EvCallbackData {
pub recdpy: XDisplay,
pub ctx: XRecordContext,
pub working: bool,
pub last_event: Time,
pub last_event: xlib::Time,
pub pos: Position<i16>,
pub stop_key: Keycode,
pub ev_nr: u32,
pub max_delay: Option<Time>,
pub max_delay: Option<xlib::Time>,
pub no_keypress_yet: bool,
pub moving: bool,
}
@ -30,7 +30,7 @@ impl EvCallbackData {
ctx: XRecordContext,
stop_key: Keycode,
pos: Position<i16>,
max_delay: Option<Time>,
max_delay: Option<xlib::Time>,
) -> Self {
EvCallbackData {
writer,
@ -43,16 +43,24 @@ impl EvCallbackData {
pos,
max_delay,
no_keypress_yet: true,
last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
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 fn ptr_is_moving(&self) -> bool {
self.moving
}
pub unsafe fn update_pos(&mut self, intercept_data: &mut XRecordInterceptData) -> Position<i16> {
self.pos.0 = *((intercept_data.data as usize + size_of::<i16>() * 10) as *const i16);
self.pos.1 = *((intercept_data.data as usize + size_of::<i16>() * 11) as *const i16);
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
}
@ -61,15 +69,23 @@ impl EvCallbackData {
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 {
self.writer.write(Instructions::Delay(calculate_delay(server_time, self.last_event, self.max_delay)));
self.writer.write(Instructions::Delay(calculate_delay(
server_time,
self.last_event,
self.max_delay,
)));
self.last_event = server_time;
}
}
}
fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>) -> Time {
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;
@ -83,4 +99,3 @@ fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>)
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};
pub mod x11_safe_wrapper;
pub mod xwrap;
pub mod chartbl;
pub mod macro_writer;
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;
@ -60,7 +62,9 @@ pub enum Instructions<'a> {
impl Display for Instructions<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}",
write!(
f,
"{}",
match self {
Instructions::Delay(d) => format!("Delay {}", d),
Instructions::ButtonPress(b) => format!("ButtonPress {}", b),
@ -71,8 +75,10 @@ impl Display for Instructions<'_> {
Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks),
Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
Instructions::KeySym(ks) => format!("KeySym {}", ks),
Instructions::KeyStrPress(kstr) => format!("KeyStrPress {}", kstr.to_str().unwrap()),
Instructions::KeyStrRelease(kstr) => format!("KeyStrRelease {}", kstr.to_str().unwrap()),
Instructions::KeyStrPress(kstr) =>
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::String(str) => format!("String {}", str),
Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd),

View file

@ -1,10 +1,8 @@
use std::fs::File;
use std::io;
use std::io::Write;
use crate::Instructions;
use std::{fs, io};
pub struct MacroWriter {
outfile: Box<dyn Write>,
outfile: Box<dyn io::Write>,
ignore_delay_capturing: bool,
}
@ -12,7 +10,7 @@ impl MacroWriter {
pub fn new(outfile: Option<std::path::PathBuf>, ignore_delay_capturing: bool) -> Self {
Self {
outfile: if let Some(outfile) = outfile {
Box::new(File::create(outfile).expect("Failed to create output file"))
Box::new(fs::File::create(outfile).expect("Failed to create output file"))
} else {
Box::new(io::stdout())
},
@ -22,12 +20,10 @@ impl MacroWriter {
pub fn write(&mut self, instruction: Instructions) {
if self.ignore_delay_capturing {
if let Instructions::Delay(_) = instruction {
return;
}
if let Instructions::Delay(_) = instruction {}
}
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,9 +1,18 @@
use std::{env, slice};
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::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,
@ -26,7 +35,8 @@ impl XDisplay {
name
} else {
env::var("DISPLAY").expect("DISPLAY is not set")
}).unwrap();
})
.unwrap();
let name_ptr = name.as_bytes().as_ptr();
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
@ -115,9 +125,7 @@ impl XDisplay {
}
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 {
@ -182,7 +190,14 @@ impl XDisplay {
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(
&self,
window: u64,
owner_events: bool,
pointer_mode: i32,
keyboard_mode: i32,
time: Time,
) -> i32 {
unsafe {
XGrabKeyboard(
self.ptr,
@ -204,9 +219,13 @@ impl XDisplay {
}
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 };
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
}
@ -214,7 +233,9 @@ impl XDisplay {
// 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) };
let xrec_res = unsafe {
XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1)
};
xrec_res == 0
}
@ -225,49 +246,35 @@ impl XDisplay {
}
let mut clients: XRecordClientSpec = XRecordAllClients;
let ctx: XRecordContext = unsafe {
XRecordCreateContext(
self.ptr,
0,
&mut clients,
1,
&mut protocol_ranges,
1,
)
};
let ctx: XRecordContext =
unsafe { XRecordCreateContext(self.ptr, 0, &mut clients, 1, &mut protocol_ranges, 1) };
ctx
}
pub fn enable_context(&self,
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
}
unsafe { XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0 }
}
pub fn enable_context_async(&self,
pub fn enable_context_async(
&self,
ctx: XRecordContext,
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
}
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
}
unsafe { XRecordDisableContext(self.ptr, ctx) != 0 }
}
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) {

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