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"] }
clap = { version = "3.2.4", features = ["derive"] }

View file

@ -10,35 +10,34 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
<summary><h2>:pen_ballpoint: TODOs :notepad_spiral:</h2></summary>
- [x] Playing macros (xmacro like)
- [x] Delay support
- [x] KeySym/KeyCode/KeyStr action support
- [x] MotionNotify and button support
- [x] String typing support (Not too high priority, but I'll add it some time probably)
- [x] ExecBlock/ExecNoBlock support (not high priority)
- [x] ExecBlock
- [x] ExecNoBlock
- [x] Delay support
- [x] KeySym/KeyCode/KeyStr action support
- [x] MotionNotify and button support
- [x] String typing support (Not too high priority, but I'll add it some time probably)
- [x] ExecBlock/ExecNoBlock support (not high priority)
- [x] ExecBlock
- [x] ExecNoBlock
- [x] Recording macros (xmacro like)
- [x] Delay
- [x] Keyboard actions
- [x] Mouse actions
- [x] Delay
- [x] Keyboard actions
- [x] Mouse actions
- [x] Utilities for playing macros
- [ ] Ignoring delays when playing
- [x] Event delay support
- [ ] Ignoring delays when playing
- [x] Event delay support
- [ ] 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,146 +1,174 @@
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)]
#[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>,
/// 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 args = Args::parse();
let display = get_remote(args.display);
let delay = args.delay.unwrap_or(10);
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");
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();
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 {
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);
}
}
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();
display.close();
}
fn get_remote(display_name: Option<String>) -> XDisplay {
let display = XDisplay::open(display_name);
let display = XDisplay::open(display_name);
if !display.has_xtest() {
eprintln!("XTest not supported!");
display.close();
exit(1)
}
if !display.has_xtest() {
eprintln!("XTest not supported!");
display.close();
exit(1)
}
display.grab_control();
display.sync();
display.grab_control();
display.sync();
display
display
}
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
let instruction_split: Vec<&str> = instruction.split(' ').collect();
let instruction_split: Vec<&str> = instruction.split(' ').collect();
match instruction_split[0] {
"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),
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),
}
"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);
// 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;
}
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 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);
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;
}
// 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); }
}
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,189 +1,198 @@
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)]
#[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,
/// 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 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);
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,
);
event_loop(display, recorded_display, stop_key, writer, args.max_delay);
display.close();
display.close();
}
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 potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
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);
}
if potential_err != GrabSuccess {
eprintln!("Couldn't grab keyboard!");
exit(1);
}
println!("Press the key you want to use to stop recording the macro.");
println!("Press the key you want to use to stop recording the macro.");
let stop_key;
loop {
display.allow_events(SyncPointer, CurrentTime);
let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
stop_key = ev.keycode;
break;
}
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);
display.ungrab_keyboard(CurrentTime);
display.ungrab_pointer(CurrentTime);
stop_key
stop_key
}
fn event_loop(
xdpy: XDisplay,
recdpy: XDisplay,
stop_key: Keycode,
mut writer: MacroWriter,
max_delay: Option<Time>,
xdpy: XDisplay,
recdpy: XDisplay,
stop_key: Keycode,
mut writer: MacroWriter,
max_delay: Option<Time>,
) {
let protocol_ranges = unsafe { XRecordAllocRange() };
let protocol_ranges = unsafe { XRecordAllocRange() };
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
if pointer_pos != Position(-1, -1) {
writer.write(Instructions::MotionNotify(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);
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();
}
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) };
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;
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;
}
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);
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.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 !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;
}
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);
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)
} else {
Instructions::KeyStrRelease(keyname)
});
}
}
BUTTONPRESS_U8 | BUTTONRELEASE_U8 => {
let bc: u8 = *((intercept_data.data as usize + 1) as *const u8);
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);
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)
} else {
Instructions::ButtonRelease(bc)
});
}
_ => eprintln!("Unknown event type: {:?}", ev_type)
}
}
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)
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,259 +1,259 @@
// the list is copied from https://github.com/Ortega-Dan/xmacroIncludingDelayCapturing/blob/1767fd310227dd13ae488b7d2821cc4bbf3847e0/chartbl.h#L27
pub const CHARTBL: [&str; 256] = [
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"BackSpace\0",
"Tab\0",
"\0",
"\0",
"\0",
"Return\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"Escape\0",
"\0",
"\0",
"\0",
"\0",
"space\0",
"exclam\0",
"quotedbl\0",
"numbersign\0",
"dollar\0",
"percent\0",
"ampersand\0",
"apostrophe\0",
"parenleft\0",
"parenright\0",
"asterisk\0",
"plus\0",
"comma\0",
"minus\0",
"period\0",
"slash\0",
"0\0",
"1\0",
"2\0",
"3\0",
"4\0",
"5\0",
"6\0",
"7\0",
"8\0",
"9\0",
"colon\0",
"semicolon\0",
"less\0",
"equal\0",
"greater\0",
"question\0",
"at\0",
"A\0",
"B\0",
"C\0",
"D\0",
"E\0",
"F\0",
"G\0",
"H\0",
"I\0",
"J\0",
"K\0",
"L\0",
"M\0",
"N\0",
"O\0",
"P\0",
"Q\0",
"R\0",
"S\0",
"T\0",
"U\0",
"V\0",
"W\0",
"X\0",
"Y\0",
"Z\0",
"bracketleft\0",
"backslash\0",
"bracketright\0",
"asciicircum\0",
"underscore\0",
"grave\0",
"a\0",
"b\0",
"c\0",
"d\0",
"e\0",
"f\0",
"g\0",
"h\0",
"i\0",
"j\0",
"k\0",
"l\0",
"m\0",
"n\0",
"o\0",
"p\0",
"q\0",
"r\0",
"s\0",
"t\0",
"u\0",
"v\0",
"w\0",
"x\0",
"y\0",
"z\0",
"braceleft\0",
"bar\0",
"braceright\0",
"asciitilde\0",
"Delete\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"nobreakspace\0",
"exclamdown\0",
"cent\0",
"sterling\0",
"currency\0",
"yen\0",
"brokenbar\0",
"section\0",
"diaeresis\0",
"copyright\0",
"ordfeminine\0",
"guillemotleft\0",
"notsign\0",
"hyphen\0",
"registered\0",
"macron\0",
"degree\0",
"plusminus\0",
"twosuperior\0",
"threesuperior\0",
"acute\0",
"mu\0",
"paragraph\0",
"periodcentered\0",
"cedilla\0",
"onesuperior\0",
"masculine\0",
"guillemotright\0",
"onequarter\0",
"onehalf\0",
"threequarters\0",
"questiondown\0",
"Agrave\0",
"Aacute\0",
"Acircumflex\0",
"Atilde\0",
"Adiaeresis\0",
"Aring\0",
"AE\0",
"Ccedilla\0",
"Egrave\0",
"Eacute\0",
"Ecircumflex\0",
"Ediaeresis\0",
"Igrave\0",
"Iacute\0",
"Icircumflex\0",
"Idiaeresis\0",
"ETH\0",
"Ntilde\0",
"Ograve\0",
"Oacute\0",
"Ocircumflex\0",
"Otilde\0",
"Odiaeresis\0",
"multiply\0",
"Ooblique\0",
"Ugrave\0",
"Uacute\0",
"Ucircumflex\0",
"Udiaeresis\0",
"Yacute\0",
"THORN\0",
"ssharp\0",
"agrave\0",
"aacute\0",
"acircumflex\0",
"atilde\0",
"adiaeresis\0",
"aring\0",
"ae\0",
"ccedilla\0",
"egrave\0",
"eacute\0",
"ecircumflex\0",
"ediaeresis\0",
"igrave\0",
"iacute\0",
"icircumflex\0",
"idiaeresis\0",
"eth\0",
"ntilde\0",
"ograve\0",
"oacute\0",
"ocircumflex\0",
"otilde\0",
"odiaeresis\0",
"division\0",
"oslash\0",
"ugrave\0",
"uacute\0",
"ucircumflex\0",
"udiaeresis\0",
"yacute\0",
"thorn\0",
"ydiaeresis\0",
];
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"BackSpace\0",
"Tab\0",
"\0",
"\0",
"\0",
"Return\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"Escape\0",
"\0",
"\0",
"\0",
"\0",
"space\0",
"exclam\0",
"quotedbl\0",
"numbersign\0",
"dollar\0",
"percent\0",
"ampersand\0",
"apostrophe\0",
"parenleft\0",
"parenright\0",
"asterisk\0",
"plus\0",
"comma\0",
"minus\0",
"period\0",
"slash\0",
"0\0",
"1\0",
"2\0",
"3\0",
"4\0",
"5\0",
"6\0",
"7\0",
"8\0",
"9\0",
"colon\0",
"semicolon\0",
"less\0",
"equal\0",
"greater\0",
"question\0",
"at\0",
"A\0",
"B\0",
"C\0",
"D\0",
"E\0",
"F\0",
"G\0",
"H\0",
"I\0",
"J\0",
"K\0",
"L\0",
"M\0",
"N\0",
"O\0",
"P\0",
"Q\0",
"R\0",
"S\0",
"T\0",
"U\0",
"V\0",
"W\0",
"X\0",
"Y\0",
"Z\0",
"bracketleft\0",
"backslash\0",
"bracketright\0",
"asciicircum\0",
"underscore\0",
"grave\0",
"a\0",
"b\0",
"c\0",
"d\0",
"e\0",
"f\0",
"g\0",
"h\0",
"i\0",
"j\0",
"k\0",
"l\0",
"m\0",
"n\0",
"o\0",
"p\0",
"q\0",
"r\0",
"s\0",
"t\0",
"u\0",
"v\0",
"w\0",
"x\0",
"y\0",
"z\0",
"braceleft\0",
"bar\0",
"braceright\0",
"asciitilde\0",
"Delete\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"\0",
"nobreakspace\0",
"exclamdown\0",
"cent\0",
"sterling\0",
"currency\0",
"yen\0",
"brokenbar\0",
"section\0",
"diaeresis\0",
"copyright\0",
"ordfeminine\0",
"guillemotleft\0",
"notsign\0",
"hyphen\0",
"registered\0",
"macron\0",
"degree\0",
"plusminus\0",
"twosuperior\0",
"threesuperior\0",
"acute\0",
"mu\0",
"paragraph\0",
"periodcentered\0",
"cedilla\0",
"onesuperior\0",
"masculine\0",
"guillemotright\0",
"onequarter\0",
"onehalf\0",
"threequarters\0",
"questiondown\0",
"Agrave\0",
"Aacute\0",
"Acircumflex\0",
"Atilde\0",
"Adiaeresis\0",
"Aring\0",
"AE\0",
"Ccedilla\0",
"Egrave\0",
"Eacute\0",
"Ecircumflex\0",
"Ediaeresis\0",
"Igrave\0",
"Iacute\0",
"Icircumflex\0",
"Idiaeresis\0",
"ETH\0",
"Ntilde\0",
"Ograve\0",
"Oacute\0",
"Ocircumflex\0",
"Otilde\0",
"Odiaeresis\0",
"multiply\0",
"Ooblique\0",
"Ugrave\0",
"Uacute\0",
"Ucircumflex\0",
"Udiaeresis\0",
"Yacute\0",
"THORN\0",
"ssharp\0",
"agrave\0",
"aacute\0",
"acircumflex\0",
"atilde\0",
"adiaeresis\0",
"aring\0",
"ae\0",
"ccedilla\0",
"egrave\0",
"eacute\0",
"ecircumflex\0",
"ediaeresis\0",
"igrave\0",
"iacute\0",
"icircumflex\0",
"idiaeresis\0",
"eth\0",
"ntilde\0",
"ograve\0",
"oacute\0",
"ocircumflex\0",
"otilde\0",
"odiaeresis\0",
"division\0",
"oslash\0",
"ugrave\0",
"uacute\0",
"ucircumflex\0",
"udiaeresis\0",
"yacute\0",
"thorn\0",
"ydiaeresis\0",
];

View file

@ -1,86 +1,101 @@
use std::mem::size_of;
use std::time::{SystemTime, UNIX_EPOCH};
use x11::xlib::Time;
use x11::xrecord::{XRecordContext, XRecordInterceptData};
use crate::{Instructions, Keycode, Position};
use crate::macro_writer::MacroWriter;
use crate::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: Time,
pub pos: Position<i16>,
pub stop_key: Keycode,
pub ev_nr: u32,
pub max_delay: Option<Time>,
pub no_keypress_yet: bool,
pub moving: bool,
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<Time>,
) -> Self {
EvCallbackData {
writer,
xdpy,
recdpy,
ctx,
stop_key,
ev_nr: 0,
working: true,
pos,
max_delay,
no_keypress_yet: true,
last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
moving: false,
}
}
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 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);
self.pos
}
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 write_pos(&mut self) {
self.writer.write(Instructions::MotionNotify(self.pos));
self.moving = false;
}
pub fn maybe_write_delay(&mut self, server_time: 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;
}
}
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: Time, last_event: Time, max_delay: Option<Time>) -> Time {
if let Some(max) = max_delay {
let max = max as u64;
let delay = server_time - last_event;
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
}
if delay > max {
max
} else {
delay
}
} else {
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;
@ -19,11 +21,11 @@ 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);
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)
Self(pos.0 as i16, pos.1 as i16)
}
}
@ -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,33 +1,29 @@
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>,
ignore_delay_capturing: bool,
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(File::create(outfile).expect("Failed to create output file"))
} else {
Box::new(io::stdout())
},
ignore_delay_capturing,
}
}
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 {
return;
}
}
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");
}
writeln!(&mut self.outfile, "{}", instruction)
.expect("Failed to write instruction to outfile");
}
}

View file

@ -1,17 +1,26 @@
use std::{env, slice};
use std::ffi::{CStr, CString};
use std::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,
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
XTestQueryExtension,
};
#[derive(Debug, Clone, Copy)]
pub struct XDisplay {
ptr: *mut Display,
ptr: *mut Display,
}
pub type Keysym = c_ulong;
@ -21,268 +30,266 @@ 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) };
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 }
}
Self { ptr: display_ptr }
}
pub fn close(self) {
unsafe { XCloseDisplay(self.ptr) };
}
pub fn close(self) {
unsafe { XCloseDisplay(self.ptr) };
}
pub fn sync(&self) {
unsafe {
XSync(self.ptr, c_int::from(false));
}
}
pub fn sync(&self) {
unsafe {
XSync(self.ptr, c_int::from(false));
}
}
pub fn flush(&self) {
unsafe {
XFlush(self.ptr);
}
}
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,
)
};
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
}
r
}
pub fn get_default_screen(&self) -> c_int {
unsafe { XDefaultScreen(self.ptr) }
}
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_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,
);
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)
};
slice::from_raw_parts::<Keysym>(ptr, keysyms_per_keycode as usize)
};
r
}
r
}
pub fn convert_case(&self, keysym: Keysym) -> (Keysym, Keysym) {
let mut keysym_lower: Keysym = Keysym::default();
let mut keysym_upper: Keysym = Keysym::default();
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);
}
unsafe {
XConvertCase(keysym, &mut keysym_lower, &mut keysym_upper);
}
(keysym_lower, keysym_upper)
}
(keysym_lower, keysym_upper)
}
pub fn keysym_to_keycode(&self, keysym: Keysym) -> Keycode {
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
}
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 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_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))
}
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
}
// 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_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_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_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_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_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_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_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_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 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 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 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 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_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 };
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); }
unsafe {
XWindowEvent(self.ptr, window, event_mask, &mut r);
}
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
}
// 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;
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
}
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(
&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 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 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 free_context(&self, ctx: XRecordContext) -> bool {
unsafe { XRecordFreeContext(self.ptr, ctx) != 0 }
}
pub fn process_replies(&self) {
unsafe { XRecordProcessReplies(self.ptr) };
}
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) }
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)
}
}
unsafe {
let cstr = CStr::from_ptr(XKeysymToString(keysym));
CString::from(cstr)
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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