Compare commits
8 commits
main
...
developmen
Author | SHA1 | Date | |
---|---|---|---|
7ecde7cd5a | |||
8dd78c66b7 | |||
52a61e070d | |||
1b688c7883 | |||
c4ebaa35be | |||
7748132fca | |||
801f8b6265 | |||
16c0826d3a |
18 changed files with 1497 additions and 847 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -68,6 +74,7 @@ dependencies = [
|
||||||
name = "easymacros"
|
name = "easymacros"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"x11",
|
"x11",
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,5 +6,6 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.65"
|
||||||
x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
|
x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
|
||||||
clap = { version = "3.2.4", features = ["derive"] }
|
clap = { version = "3.2.4", features = ["derive"] }
|
47
README.md
47
README.md
|
@ -10,35 +10,34 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
|
||||||
<summary><h2>:pen_ballpoint: TODOs :notepad_spiral:</h2></summary>
|
<summary><h2>:pen_ballpoint: TODOs :notepad_spiral:</h2></summary>
|
||||||
|
|
||||||
- [x] Playing macros (xmacro like)
|
- [x] Playing macros (xmacro like)
|
||||||
- [x] Delay support
|
- [x] Delay support
|
||||||
- [x] KeySym/KeyCode/KeyStr action support
|
- [x] KeySym/KeyCode/KeyStr action support
|
||||||
- [x] MotionNotify and button support
|
- [x] MotionNotify and button support
|
||||||
- [x] String typing support (Not too high priority, but I'll add it some time probably)
|
- [x] String typing support (Not too high priority, but I'll add it some time probably)
|
||||||
- [x] ExecBlock/ExecNoBlock support (not high priority)
|
- [x] ExecBlock/ExecNoBlock support (not high priority)
|
||||||
- [x] ExecBlock
|
- [x] ExecBlock
|
||||||
- [x] ExecNoBlock
|
- [x] ExecNoBlock
|
||||||
- [x] Recording macros (xmacro like)
|
- [x] Recording macros (xmacro like)
|
||||||
- [x] Delay
|
- [x] Delay
|
||||||
- [x] Keyboard actions
|
- [x] Keyboard actions
|
||||||
- [x] Mouse actions
|
- [x] Mouse actions
|
||||||
- [x] Utilities for playing macros
|
- [x] Utilities for playing macros
|
||||||
- [ ] Ignoring delays when playing
|
- [ ] Ignoring delays when playing
|
||||||
- [x] Event delay support
|
- [x] Event delay support
|
||||||
- [ ] Rebrand?
|
- [ ] Rebrand?
|
||||||
- [ ] new name
|
- [ ] new name
|
||||||
- [ ] logo
|
- [ ] logo
|
||||||
- [ ] macro language (easymacros daemon?)
|
- [ ] Listening/remapping
|
||||||
- [ ] basic interpreter/compiler to fast intermediate lang
|
- [ ] Modes
|
||||||
- [ ] stdlib
|
- [ ] Way to show current mode
|
||||||
- [ ] xlib stuff (get window handles/ids)
|
- [ ] mode change notifications?
|
||||||
- [ ] easy xtst/xrecord etc abstractions
|
- [ ] small gui/popups?
|
||||||
- [ ] number/text inputs
|
- [ ] allow passing keys through in some modes
|
||||||
- [ ] clipboard
|
- [ ] make modes listen for numbers/amounts of repetitions
|
||||||
- [ ] basic gui features
|
- [ ] make shortcuts be able to listen for other shortcuts and inputs
|
||||||
- [ ] filesystem stuff
|
- [ ] rofi integration
|
||||||
- [ ] shell stuff
|
- [ ] autorun stuff on some windows?
|
||||||
- [ ] find image/track image/wait for image...
|
- [ ] Proper, safe xlib wrapper
|
||||||
- [ ] event listeners
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
|
@ -1,146 +1,174 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay};
|
use easymacros::chartbl::CHARTBL;
|
||||||
|
use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::process::{Command, exit};
|
use std::io::stdin;
|
||||||
|
use std::process::{exit, Command};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, thread};
|
use std::{fs, thread};
|
||||||
use std::io::stdin;
|
|
||||||
use x11::keysym::XK_Shift_L;
|
use x11::keysym::XK_Shift_L;
|
||||||
use easymacros::chartbl::CHARTBL;
|
|
||||||
|
|
||||||
/// Macro player module for easymacros. It's compatible with xmacro macros.
|
/// Macro player module for easymacros. It's compatible with xmacro macros.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// The file that contains the macro to run.
|
/// The file that contains the macro to run.
|
||||||
#[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)]
|
#[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)]
|
||||||
input_file: Option<std::path::PathBuf>,
|
input_file: Option<std::path::PathBuf>,
|
||||||
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
|
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
|
||||||
#[clap(short = 'D', long)]
|
#[clap(short = 'D', long)]
|
||||||
display: Option<String>,
|
display: Option<String>,
|
||||||
/// Delay for events to be sent.
|
/// Delay for events to be sent.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
delay: Option<u64>,
|
delay: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let display = get_remote(args.display);
|
let display = get_remote(args.display);
|
||||||
let delay = args.delay.unwrap_or(10);
|
let delay = args.delay.unwrap_or(10);
|
||||||
|
|
||||||
if let Some(input_file_path) = args.input_file {
|
if let Some(input_file_path) = args.input_file {
|
||||||
let input_file_contents = fs::read_to_string(input_file_path).expect("Couldn't read macro file");
|
let input_file_contents =
|
||||||
|
fs::read_to_string(input_file_path).expect("Couldn't read macro file");
|
||||||
|
|
||||||
for instruction in input_file_contents.lines() {
|
for instruction in input_file_contents.lines() {
|
||||||
run_instruction(instruction, &display, delay);
|
run_instruction(instruction, &display, delay);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("No input file specified, reading from stdin.");
|
println!("No input file specified, reading from stdin.");
|
||||||
let stdin = stdin();
|
let stdin = stdin();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
// TODO: Unify with macro_writer using trait objects
|
||||||
stdin.read_line(&mut line).expect("Couldn't read line from stdin");
|
let mut line = String::new();
|
||||||
// Without this it crashes because apparently it doesn't properly read the next input line?
|
stdin
|
||||||
println!();
|
.read_line(&mut line)
|
||||||
line = line.trim().to_string();
|
.expect("Couldn't read line from stdin");
|
||||||
run_instruction(&*line, &display, delay);
|
// Without this it crashes because apparently it doesn't properly read the next input line?
|
||||||
}
|
println!();
|
||||||
}
|
line = line.trim().to_string();
|
||||||
|
run_instruction(&*line, &display, delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
display.close();
|
display.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_remote(display_name: Option<String>) -> XDisplay {
|
fn get_remote(display_name: Option<String>) -> XDisplay {
|
||||||
let display = XDisplay::open(display_name);
|
let display = XDisplay::open(display_name);
|
||||||
|
|
||||||
if !display.has_xtest() {
|
if !display.has_xtest() {
|
||||||
eprintln!("XTest not supported!");
|
eprintln!("XTest not supported!");
|
||||||
display.close();
|
display.close();
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
display.grab_control();
|
display.grab_control();
|
||||||
display.sync();
|
display.sync();
|
||||||
|
|
||||||
display
|
display
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
|
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
|
||||||
let instruction_split: Vec<&str> = instruction.split(' ').collect();
|
let instruction_split: Vec<&str> = instruction.split(' ').collect();
|
||||||
|
|
||||||
match instruction_split[0] {
|
match instruction_split[0] {
|
||||||
"Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
|
"Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
|
||||||
"ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
|
"ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
|
||||||
"ButtonRelease" => dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay),
|
"ButtonRelease" => {
|
||||||
"MotionNotify" => dpy.send_fake_motion_event(instruction_split[1].parse().unwrap(), instruction_split[2].parse().unwrap(), delay),
|
dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay)
|
||||||
"KeyCodePress" => dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay),
|
}
|
||||||
"KeyCodeRelease" => dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay),
|
"MotionNotify" => dpy.send_fake_motion_event(
|
||||||
"KeySymPress" => dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay),
|
instruction_split[1].parse().unwrap(),
|
||||||
"KeySymRelease" => dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay),
|
instruction_split[2].parse().unwrap(),
|
||||||
|
delay,
|
||||||
|
),
|
||||||
|
"KeyCodePress" => {
|
||||||
|
dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay)
|
||||||
|
}
|
||||||
|
"KeyCodeRelease" => {
|
||||||
|
dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay)
|
||||||
|
}
|
||||||
|
"KeySymPress" => {
|
||||||
|
dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay)
|
||||||
|
}
|
||||||
|
"KeySymRelease" => {
|
||||||
|
dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay)
|
||||||
|
}
|
||||||
|
|
||||||
"KeySym" => {
|
"KeySym" => {
|
||||||
let key: Keysym = instruction_split[1].parse().unwrap();
|
let key: Keysym = instruction_split[1].parse().unwrap();
|
||||||
dpy.send_fake_keypress_from_keysym(key, delay);
|
dpy.send_fake_keypress_from_keysym(key, delay);
|
||||||
dpy.send_fake_keyrelease_from_keysym(key, delay);
|
dpy.send_fake_keyrelease_from_keysym(key, delay);
|
||||||
}
|
}
|
||||||
"KeyStrPress" => dpy.send_fake_keypress_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
|
"KeyStrPress" => dpy.send_fake_keypress_from_string(
|
||||||
"KeyStrRelease" => dpy.send_fake_keyrelease_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
|
CString::new(instruction_split[1]).unwrap().as_bytes(),
|
||||||
"KeyStr" => {
|
delay,
|
||||||
let keystring = CString::new(instruction_split[1]).unwrap();
|
),
|
||||||
dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
|
"KeyStrRelease" => dpy.send_fake_keyrelease_from_string(
|
||||||
dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
|
CString::new(instruction_split[1]).unwrap().as_bytes(),
|
||||||
}
|
delay,
|
||||||
"String" => {
|
),
|
||||||
for c in instruction["String".len() + 1..].chars() {
|
"KeyStr" => {
|
||||||
send_char(dpy, c, delay);
|
let keystring = CString::new(instruction_split[1]).unwrap();
|
||||||
}
|
dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
|
||||||
}
|
dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
|
||||||
"ExecBlock" | "ExecNoBlock" => {
|
}
|
||||||
let mut command = Command::new(instruction_split[1]);
|
"String" => {
|
||||||
for arg in &instruction_split[2..] {
|
for c in instruction["String".len() + 1..].chars() {
|
||||||
command.arg(arg);
|
send_char(dpy, c, delay);
|
||||||
}
|
}
|
||||||
if instruction_split[0] == "ExecBlock" {
|
}
|
||||||
command.status().unwrap();
|
"ExecBlock" | "ExecNoBlock" => {
|
||||||
} else {
|
let mut command = Command::new(instruction_split[1]);
|
||||||
command.spawn().unwrap();
|
for arg in &instruction_split[2..] {
|
||||||
}
|
command.arg(arg);
|
||||||
}
|
}
|
||||||
c => panic!("Unknown command {:?}", instruction_split),
|
if instruction_split[0] == "ExecBlock" {
|
||||||
}
|
command.status().unwrap();
|
||||||
|
} else {
|
||||||
|
command.spawn().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c => panic!("Unknown command {:?}", instruction_split),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_char(dpy: &XDisplay, c: char, delay: u64) {
|
fn send_char(dpy: &XDisplay, c: char, delay: u64) {
|
||||||
// get keystring from character and turn it into a keysym
|
// get keystring from character and turn it into a keysym
|
||||||
let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
|
let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
|
||||||
let keycode = dpy.keysym_to_keycode(keysym);
|
let keycode = dpy.keysym_to_keycode(keysym);
|
||||||
|
|
||||||
if keycode == 0 {
|
if keycode == 0 {
|
||||||
eprintln!("No keycode found for character '{}'", c);
|
eprintln!("No keycode found for character '{}'", c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let map_ks = dpy.get_keyboard_mapping(keycode, 1);
|
let map_ks = dpy.get_keyboard_mapping(keycode, 1);
|
||||||
if map_ks[0] == 0 {
|
if map_ks[0] == 0 {
|
||||||
eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
|
eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (ks_lower, ks_upper) = dpy.convert_case(keysym);
|
let (ks_lower, ks_upper) = dpy.convert_case(keysym);
|
||||||
|
|
||||||
// check if shift has to be pressed as well
|
// check if shift has to be pressed as well
|
||||||
let mut shift_needed = true;
|
let mut shift_needed = true;
|
||||||
if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
|
if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
|
||||||
shift_needed = false;
|
shift_needed = false;
|
||||||
}
|
}
|
||||||
if keysym == ks_lower && keysym != ks_upper {
|
if keysym == ks_lower && keysym != ks_upper {
|
||||||
shift_needed = false;
|
shift_needed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if shift_needed { dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); }
|
if shift_needed {
|
||||||
dpy.send_fake_keypress_from_code(keycode, delay);
|
dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay);
|
||||||
dpy.send_fake_keyrelease_from_code(keycode, delay);
|
}
|
||||||
if shift_needed { dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); }
|
dpy.send_fake_keypress_from_code(keycode, delay);
|
||||||
|
dpy.send_fake_keyrelease_from_code(keycode, delay);
|
||||||
|
if shift_needed {
|
||||||
|
dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,189 +1,198 @@
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::os::raw::{c_char};
|
use std::os::raw::c_char;
|
||||||
use std::process::{exit};
|
use std::process::exit;
|
||||||
use std::ptr::addr_of;
|
use std::ptr::addr_of;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, XKeyEvent};
|
use x11::xlib::{
|
||||||
use x11::xrecord::{XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData};
|
CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree,
|
||||||
|
XKeyEvent,
|
||||||
|
};
|
||||||
|
use x11::xrecord::{
|
||||||
|
XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData,
|
||||||
|
};
|
||||||
|
|
||||||
use easymacros::{BUTTONPRESS_U8, BUTTONRELEASE_U8, Instructions, KEYPRESS_U8, KEYRELEASE_U8, MOTIONNOTIFY_U8, Position};
|
|
||||||
use easymacros::ev_callback_data::EvCallbackData;
|
use easymacros::ev_callback_data::EvCallbackData;
|
||||||
use easymacros::macro_writer::MacroWriter;
|
use easymacros::macro_writer::MacroWriter;
|
||||||
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
|
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
|
||||||
|
use easymacros::{
|
||||||
|
Instructions, Position, BUTTONPRESS_U8, BUTTONRELEASE_U8, KEYPRESS_U8, KEYRELEASE_U8,
|
||||||
|
MOTIONNOTIFY_U8,
|
||||||
|
};
|
||||||
|
|
||||||
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
|
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// The file to record the macro to. Defaults to writing to stdout.
|
/// The file to record the macro to. Defaults to writing to stdout.
|
||||||
#[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)]
|
#[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)]
|
||||||
output_file: Option<std::path::PathBuf>,
|
output_file: Option<std::path::PathBuf>,
|
||||||
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
|
/// Display to run the macro on. This uses the $DISPLAY environment variable by default.
|
||||||
#[clap(short = 'D', long)]
|
#[clap(short = 'D', long)]
|
||||||
display: Option<String>,
|
display: Option<String>,
|
||||||
/// Max Delay in milliseconds for macro delays
|
/// Max Delay in milliseconds for macro delays
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
max_delay: Option<u64>,
|
max_delay: Option<u64>,
|
||||||
/// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay.
|
/// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay.
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
ignore_delay_capturing: bool,
|
ignore_delay_capturing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let display = XDisplay::open(args.display.clone());
|
let display = XDisplay::open(args.display.clone());
|
||||||
let recorded_display = XDisplay::open(args.display.clone());
|
let recorded_display = XDisplay::open(args.display.clone());
|
||||||
let stop_key = get_stop_key(display);
|
let stop_key = get_stop_key(display);
|
||||||
let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
|
let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
|
||||||
|
|
||||||
event_loop(
|
event_loop(display, recorded_display, stop_key, writer, args.max_delay);
|
||||||
display,
|
|
||||||
recorded_display,
|
|
||||||
stop_key,
|
|
||||||
writer,
|
|
||||||
args.max_delay,
|
|
||||||
);
|
|
||||||
|
|
||||||
display.close();
|
display.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_stop_key(display: XDisplay) -> Keycode {
|
fn get_stop_key(display: XDisplay) -> Keycode {
|
||||||
let screen = display.get_default_screen();
|
let screen = display.get_default_screen();
|
||||||
|
|
||||||
let root = display.get_root_window(screen);
|
let root = display.get_root_window(screen);
|
||||||
let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
|
let potential_err =
|
||||||
|
display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
|
||||||
|
|
||||||
if potential_err != GrabSuccess {
|
if potential_err != GrabSuccess {
|
||||||
eprintln!("Couldn't grab keyboard!");
|
eprintln!("Couldn't grab keyboard!");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Press the key you want to use to stop recording the macro.");
|
println!("Press the key you want to use to stop recording the macro.");
|
||||||
|
|
||||||
let stop_key;
|
let stop_key;
|
||||||
loop {
|
loop {
|
||||||
display.allow_events(SyncPointer, CurrentTime);
|
display.allow_events(SyncPointer, CurrentTime);
|
||||||
let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
|
let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
|
||||||
stop_key = ev.keycode;
|
stop_key = ev.keycode;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
display.ungrab_keyboard(CurrentTime);
|
display.ungrab_keyboard(CurrentTime);
|
||||||
display.ungrab_pointer(CurrentTime);
|
display.ungrab_pointer(CurrentTime);
|
||||||
|
|
||||||
stop_key
|
stop_key
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_loop(
|
fn event_loop(
|
||||||
xdpy: XDisplay,
|
xdpy: XDisplay,
|
||||||
recdpy: XDisplay,
|
recdpy: XDisplay,
|
||||||
stop_key: Keycode,
|
stop_key: Keycode,
|
||||||
mut writer: MacroWriter,
|
mut writer: MacroWriter,
|
||||||
max_delay: Option<Time>,
|
max_delay: Option<Time>,
|
||||||
) {
|
) {
|
||||||
let protocol_ranges = unsafe { XRecordAllocRange() };
|
let protocol_ranges = unsafe { XRecordAllocRange() };
|
||||||
|
|
||||||
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
|
let pointer_pos: Position<i16> = Position::from(xdpy.query_pointer_pos());
|
||||||
|
|
||||||
if pointer_pos != Position(-1, -1) {
|
if pointer_pos != Position(-1, -1) {
|
||||||
writer.write(Instructions::MotionNotify(pointer_pos))
|
writer.write(Instructions::MotionNotify(pointer_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctx = recdpy.create_record_context(protocol_ranges);
|
let ctx = recdpy.create_record_context(protocol_ranges);
|
||||||
let ev_cb_data = EvCallbackData::new(writer, xdpy, recdpy, ctx, stop_key, pointer_pos, max_delay);
|
let ev_cb_data =
|
||||||
|
EvCallbackData::new(writer, xdpy, recdpy, ctx, stop_key, pointer_pos, max_delay);
|
||||||
|
|
||||||
if !recdpy.enable_context_async(ctx, Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char) {
|
if !recdpy.enable_context_async(ctx, Some(ev_callback), addr_of!(ev_cb_data) as *mut c_char) {
|
||||||
panic!("Failed to enable record context")
|
panic!("Failed to enable record context")
|
||||||
}
|
}
|
||||||
while ev_cb_data.working {
|
while ev_cb_data.working {
|
||||||
recdpy.process_replies();
|
recdpy.process_replies();
|
||||||
}
|
}
|
||||||
|
|
||||||
xdpy.disable_context(ctx);
|
xdpy.disable_context(ctx);
|
||||||
xdpy.free_context(ctx);
|
xdpy.free_context(ctx);
|
||||||
unsafe { XFree(protocol_ranges as *mut c_void) };
|
unsafe { XFree(protocol_ranges as *mut c_void) };
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRecordInterceptData) {
|
unsafe extern "C" fn ev_callback(closure: *mut c_char, intercept_data: *mut XRecordInterceptData) {
|
||||||
let data = &mut *(closure as *mut EvCallbackData);
|
let data = &mut *(closure as *mut EvCallbackData);
|
||||||
let intercept_data = &mut *intercept_data;
|
let intercept_data = &mut *intercept_data;
|
||||||
|
|
||||||
if intercept_data.category == XRecordStartOfData {
|
if intercept_data.category == XRecordStartOfData {
|
||||||
println!("Got start of data!");
|
println!("Got start of data!");
|
||||||
data.last_event = intercept_data.server_time;
|
data.last_event = intercept_data.server_time;
|
||||||
XRecordFreeData(intercept_data);
|
XRecordFreeData(intercept_data);
|
||||||
return;
|
return;
|
||||||
} else if intercept_data.category == XRecordEndOfData {
|
} else if intercept_data.category == XRecordEndOfData {
|
||||||
println!("Got end of data!");
|
println!("Got end of data!");
|
||||||
XRecordFreeData(intercept_data);
|
XRecordFreeData(intercept_data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ev_type = *(intercept_data.data as *const u8);
|
let ev_type = *(intercept_data.data as *const u8);
|
||||||
|
|
||||||
if data.pos.0 == 0 || data.pos.1 == -1 {
|
if data.pos.0 == 0 || data.pos.1 == -1 {
|
||||||
if ev_type == MOTIONNOTIFY_U8 {
|
if ev_type == MOTIONNOTIFY_U8 {
|
||||||
data.update_pos(intercept_data);
|
data.update_pos(intercept_data);
|
||||||
data.write_pos();
|
data.write_pos();
|
||||||
} else {
|
} else {
|
||||||
println!("Move your cursor so the macro can start with a fixed cursor position!
|
println!(
|
||||||
Skipping event...");
|
"Move your cursor so the macro can start with a fixed cursor position!
|
||||||
}
|
Skipping event..."
|
||||||
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
|
);
|
||||||
println!("Skipping KeyRelease without recorded KeyPress...");
|
}
|
||||||
} else {
|
} else if data.no_keypress_yet && ev_type == KEYRELEASE_U8 {
|
||||||
match ev_type {
|
println!("Skipping KeyRelease without recorded KeyPress...");
|
||||||
MOTIONNOTIFY_U8 => {
|
} else {
|
||||||
data.update_pos(intercept_data);
|
match ev_type {
|
||||||
|
MOTIONNOTIFY_U8 => {
|
||||||
|
data.update_pos(intercept_data);
|
||||||
|
|
||||||
if !data.moving {
|
if !data.moving {
|
||||||
data.moving = true;
|
data.moving = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KEYPRESS_U8 | KEYRELEASE_U8 => {
|
KEYPRESS_U8 | KEYRELEASE_U8 => {
|
||||||
let kc: u8 = *((intercept_data.data as usize + 1) as *const u8);
|
let kc: u8 = *((intercept_data.data as usize + 1) as *const u8);
|
||||||
let keyname = data.xdpy.keycode_to_string(kc as u32);
|
let keyname = data.xdpy.keycode_to_string(kc as u32);
|
||||||
|
|
||||||
if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 {
|
if ev_type == KEYPRESS_U8 && kc == data.stop_key as u8 {
|
||||||
data.working = false;
|
data.working = false;
|
||||||
} else {
|
} else {
|
||||||
if ev_type == KEYPRESS_U8 {
|
if ev_type == KEYPRESS_U8 {
|
||||||
data.no_keypress_yet = false;
|
data.no_keypress_yet = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.maybe_write_delay(intercept_data.server_time);
|
data.maybe_write_delay(intercept_data.server_time);
|
||||||
|
|
||||||
if data.ptr_is_moving() { data.write_pos(); }
|
if data.ptr_is_moving() {
|
||||||
|
data.write_pos();
|
||||||
|
}
|
||||||
|
|
||||||
data.writer.write(if ev_type == KEYPRESS_U8 {
|
data.writer.write(if ev_type == KEYPRESS_U8 {
|
||||||
Instructions::KeyStrPress(keyname)
|
Instructions::KeyStrPress(keyname)
|
||||||
} else {
|
} else {
|
||||||
Instructions::KeyStrRelease(keyname)
|
Instructions::KeyStrRelease(keyname)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BUTTONPRESS_U8 | BUTTONRELEASE_U8 => {
|
BUTTONPRESS_U8 | BUTTONRELEASE_U8 => {
|
||||||
let bc: u8 = *((intercept_data.data as usize + 1) as *const u8);
|
let bc: u8 = *((intercept_data.data as usize + 1) as *const u8);
|
||||||
|
|
||||||
data.maybe_write_delay(intercept_data.server_time);
|
data.maybe_write_delay(intercept_data.server_time);
|
||||||
|
|
||||||
if data.ptr_is_moving() { data.write_pos(); }
|
if data.ptr_is_moving() {
|
||||||
|
data.write_pos();
|
||||||
|
}
|
||||||
|
|
||||||
data.writer.write(if ev_type == BUTTONPRESS_U8 {
|
data.writer.write(if ev_type == BUTTONPRESS_U8 {
|
||||||
Instructions::ButtonPress(bc)
|
Instructions::ButtonPress(bc)
|
||||||
} else {
|
} else {
|
||||||
Instructions::ButtonRelease(bc)
|
Instructions::ButtonRelease(bc)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_ => eprintln!("Unknown event type: {:?}", ev_type)
|
_ => eprintln!("Unknown event type: {:?}", ev_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.ev_nr += 2;
|
data.ev_nr += 2;
|
||||||
XRecordFreeData(intercept_data)
|
XRecordFreeData(intercept_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
63
src/bin/emrec.rs
Normal file
63
src/bin/emrec.rs
Normal 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!()
|
||||||
|
}
|
512
src/chartbl.rs
512
src/chartbl.rs
|
@ -1,259 +1,259 @@
|
||||||
// the list is copied from https://github.com/Ortega-Dan/xmacroIncludingDelayCapturing/blob/1767fd310227dd13ae488b7d2821cc4bbf3847e0/chartbl.h#L27
|
// the list is copied from https://github.com/Ortega-Dan/xmacroIncludingDelayCapturing/blob/1767fd310227dd13ae488b7d2821cc4bbf3847e0/chartbl.h#L27
|
||||||
pub const CHARTBL: [&str; 256] = [
|
pub const CHARTBL: [&str; 256] = [
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"BackSpace\0",
|
"BackSpace\0",
|
||||||
"Tab\0",
|
"Tab\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"Return\0",
|
"Return\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"Escape\0",
|
"Escape\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"space\0",
|
"space\0",
|
||||||
"exclam\0",
|
"exclam\0",
|
||||||
"quotedbl\0",
|
"quotedbl\0",
|
||||||
"numbersign\0",
|
"numbersign\0",
|
||||||
"dollar\0",
|
"dollar\0",
|
||||||
"percent\0",
|
"percent\0",
|
||||||
"ampersand\0",
|
"ampersand\0",
|
||||||
"apostrophe\0",
|
"apostrophe\0",
|
||||||
"parenleft\0",
|
"parenleft\0",
|
||||||
"parenright\0",
|
"parenright\0",
|
||||||
"asterisk\0",
|
"asterisk\0",
|
||||||
"plus\0",
|
"plus\0",
|
||||||
"comma\0",
|
"comma\0",
|
||||||
"minus\0",
|
"minus\0",
|
||||||
"period\0",
|
"period\0",
|
||||||
"slash\0",
|
"slash\0",
|
||||||
"0\0",
|
"0\0",
|
||||||
"1\0",
|
"1\0",
|
||||||
"2\0",
|
"2\0",
|
||||||
"3\0",
|
"3\0",
|
||||||
"4\0",
|
"4\0",
|
||||||
"5\0",
|
"5\0",
|
||||||
"6\0",
|
"6\0",
|
||||||
"7\0",
|
"7\0",
|
||||||
"8\0",
|
"8\0",
|
||||||
"9\0",
|
"9\0",
|
||||||
"colon\0",
|
"colon\0",
|
||||||
"semicolon\0",
|
"semicolon\0",
|
||||||
"less\0",
|
"less\0",
|
||||||
"equal\0",
|
"equal\0",
|
||||||
"greater\0",
|
"greater\0",
|
||||||
"question\0",
|
"question\0",
|
||||||
"at\0",
|
"at\0",
|
||||||
"A\0",
|
"A\0",
|
||||||
"B\0",
|
"B\0",
|
||||||
"C\0",
|
"C\0",
|
||||||
"D\0",
|
"D\0",
|
||||||
"E\0",
|
"E\0",
|
||||||
"F\0",
|
"F\0",
|
||||||
"G\0",
|
"G\0",
|
||||||
"H\0",
|
"H\0",
|
||||||
"I\0",
|
"I\0",
|
||||||
"J\0",
|
"J\0",
|
||||||
"K\0",
|
"K\0",
|
||||||
"L\0",
|
"L\0",
|
||||||
"M\0",
|
"M\0",
|
||||||
"N\0",
|
"N\0",
|
||||||
"O\0",
|
"O\0",
|
||||||
"P\0",
|
"P\0",
|
||||||
"Q\0",
|
"Q\0",
|
||||||
"R\0",
|
"R\0",
|
||||||
"S\0",
|
"S\0",
|
||||||
"T\0",
|
"T\0",
|
||||||
"U\0",
|
"U\0",
|
||||||
"V\0",
|
"V\0",
|
||||||
"W\0",
|
"W\0",
|
||||||
"X\0",
|
"X\0",
|
||||||
"Y\0",
|
"Y\0",
|
||||||
"Z\0",
|
"Z\0",
|
||||||
"bracketleft\0",
|
"bracketleft\0",
|
||||||
"backslash\0",
|
"backslash\0",
|
||||||
"bracketright\0",
|
"bracketright\0",
|
||||||
"asciicircum\0",
|
"asciicircum\0",
|
||||||
"underscore\0",
|
"underscore\0",
|
||||||
"grave\0",
|
"grave\0",
|
||||||
"a\0",
|
"a\0",
|
||||||
"b\0",
|
"b\0",
|
||||||
"c\0",
|
"c\0",
|
||||||
"d\0",
|
"d\0",
|
||||||
"e\0",
|
"e\0",
|
||||||
"f\0",
|
"f\0",
|
||||||
"g\0",
|
"g\0",
|
||||||
"h\0",
|
"h\0",
|
||||||
"i\0",
|
"i\0",
|
||||||
"j\0",
|
"j\0",
|
||||||
"k\0",
|
"k\0",
|
||||||
"l\0",
|
"l\0",
|
||||||
"m\0",
|
"m\0",
|
||||||
"n\0",
|
"n\0",
|
||||||
"o\0",
|
"o\0",
|
||||||
"p\0",
|
"p\0",
|
||||||
"q\0",
|
"q\0",
|
||||||
"r\0",
|
"r\0",
|
||||||
"s\0",
|
"s\0",
|
||||||
"t\0",
|
"t\0",
|
||||||
"u\0",
|
"u\0",
|
||||||
"v\0",
|
"v\0",
|
||||||
"w\0",
|
"w\0",
|
||||||
"x\0",
|
"x\0",
|
||||||
"y\0",
|
"y\0",
|
||||||
"z\0",
|
"z\0",
|
||||||
"braceleft\0",
|
"braceleft\0",
|
||||||
"bar\0",
|
"bar\0",
|
||||||
"braceright\0",
|
"braceright\0",
|
||||||
"asciitilde\0",
|
"asciitilde\0",
|
||||||
"Delete\0",
|
"Delete\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"\0",
|
"\0",
|
||||||
"nobreakspace\0",
|
"nobreakspace\0",
|
||||||
"exclamdown\0",
|
"exclamdown\0",
|
||||||
"cent\0",
|
"cent\0",
|
||||||
"sterling\0",
|
"sterling\0",
|
||||||
"currency\0",
|
"currency\0",
|
||||||
"yen\0",
|
"yen\0",
|
||||||
"brokenbar\0",
|
"brokenbar\0",
|
||||||
"section\0",
|
"section\0",
|
||||||
"diaeresis\0",
|
"diaeresis\0",
|
||||||
"copyright\0",
|
"copyright\0",
|
||||||
"ordfeminine\0",
|
"ordfeminine\0",
|
||||||
"guillemotleft\0",
|
"guillemotleft\0",
|
||||||
"notsign\0",
|
"notsign\0",
|
||||||
"hyphen\0",
|
"hyphen\0",
|
||||||
"registered\0",
|
"registered\0",
|
||||||
"macron\0",
|
"macron\0",
|
||||||
"degree\0",
|
"degree\0",
|
||||||
"plusminus\0",
|
"plusminus\0",
|
||||||
"twosuperior\0",
|
"twosuperior\0",
|
||||||
"threesuperior\0",
|
"threesuperior\0",
|
||||||
"acute\0",
|
"acute\0",
|
||||||
"mu\0",
|
"mu\0",
|
||||||
"paragraph\0",
|
"paragraph\0",
|
||||||
"periodcentered\0",
|
"periodcentered\0",
|
||||||
"cedilla\0",
|
"cedilla\0",
|
||||||
"onesuperior\0",
|
"onesuperior\0",
|
||||||
"masculine\0",
|
"masculine\0",
|
||||||
"guillemotright\0",
|
"guillemotright\0",
|
||||||
"onequarter\0",
|
"onequarter\0",
|
||||||
"onehalf\0",
|
"onehalf\0",
|
||||||
"threequarters\0",
|
"threequarters\0",
|
||||||
"questiondown\0",
|
"questiondown\0",
|
||||||
"Agrave\0",
|
"Agrave\0",
|
||||||
"Aacute\0",
|
"Aacute\0",
|
||||||
"Acircumflex\0",
|
"Acircumflex\0",
|
||||||
"Atilde\0",
|
"Atilde\0",
|
||||||
"Adiaeresis\0",
|
"Adiaeresis\0",
|
||||||
"Aring\0",
|
"Aring\0",
|
||||||
"AE\0",
|
"AE\0",
|
||||||
"Ccedilla\0",
|
"Ccedilla\0",
|
||||||
"Egrave\0",
|
"Egrave\0",
|
||||||
"Eacute\0",
|
"Eacute\0",
|
||||||
"Ecircumflex\0",
|
"Ecircumflex\0",
|
||||||
"Ediaeresis\0",
|
"Ediaeresis\0",
|
||||||
"Igrave\0",
|
"Igrave\0",
|
||||||
"Iacute\0",
|
"Iacute\0",
|
||||||
"Icircumflex\0",
|
"Icircumflex\0",
|
||||||
"Idiaeresis\0",
|
"Idiaeresis\0",
|
||||||
"ETH\0",
|
"ETH\0",
|
||||||
"Ntilde\0",
|
"Ntilde\0",
|
||||||
"Ograve\0",
|
"Ograve\0",
|
||||||
"Oacute\0",
|
"Oacute\0",
|
||||||
"Ocircumflex\0",
|
"Ocircumflex\0",
|
||||||
"Otilde\0",
|
"Otilde\0",
|
||||||
"Odiaeresis\0",
|
"Odiaeresis\0",
|
||||||
"multiply\0",
|
"multiply\0",
|
||||||
"Ooblique\0",
|
"Ooblique\0",
|
||||||
"Ugrave\0",
|
"Ugrave\0",
|
||||||
"Uacute\0",
|
"Uacute\0",
|
||||||
"Ucircumflex\0",
|
"Ucircumflex\0",
|
||||||
"Udiaeresis\0",
|
"Udiaeresis\0",
|
||||||
"Yacute\0",
|
"Yacute\0",
|
||||||
"THORN\0",
|
"THORN\0",
|
||||||
"ssharp\0",
|
"ssharp\0",
|
||||||
"agrave\0",
|
"agrave\0",
|
||||||
"aacute\0",
|
"aacute\0",
|
||||||
"acircumflex\0",
|
"acircumflex\0",
|
||||||
"atilde\0",
|
"atilde\0",
|
||||||
"adiaeresis\0",
|
"adiaeresis\0",
|
||||||
"aring\0",
|
"aring\0",
|
||||||
"ae\0",
|
"ae\0",
|
||||||
"ccedilla\0",
|
"ccedilla\0",
|
||||||
"egrave\0",
|
"egrave\0",
|
||||||
"eacute\0",
|
"eacute\0",
|
||||||
"ecircumflex\0",
|
"ecircumflex\0",
|
||||||
"ediaeresis\0",
|
"ediaeresis\0",
|
||||||
"igrave\0",
|
"igrave\0",
|
||||||
"iacute\0",
|
"iacute\0",
|
||||||
"icircumflex\0",
|
"icircumflex\0",
|
||||||
"idiaeresis\0",
|
"idiaeresis\0",
|
||||||
"eth\0",
|
"eth\0",
|
||||||
"ntilde\0",
|
"ntilde\0",
|
||||||
"ograve\0",
|
"ograve\0",
|
||||||
"oacute\0",
|
"oacute\0",
|
||||||
"ocircumflex\0",
|
"ocircumflex\0",
|
||||||
"otilde\0",
|
"otilde\0",
|
||||||
"odiaeresis\0",
|
"odiaeresis\0",
|
||||||
"division\0",
|
"division\0",
|
||||||
"oslash\0",
|
"oslash\0",
|
||||||
"ugrave\0",
|
"ugrave\0",
|
||||||
"uacute\0",
|
"uacute\0",
|
||||||
"ucircumflex\0",
|
"ucircumflex\0",
|
||||||
"udiaeresis\0",
|
"udiaeresis\0",
|
||||||
"yacute\0",
|
"yacute\0",
|
||||||
"thorn\0",
|
"thorn\0",
|
||||||
"ydiaeresis\0",
|
"ydiaeresis\0",
|
||||||
];
|
];
|
|
@ -1,86 +1,101 @@
|
||||||
use std::mem::size_of;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use x11::xlib::Time;
|
|
||||||
use x11::xrecord::{XRecordContext, XRecordInterceptData};
|
|
||||||
use crate::{Instructions, Keycode, Position};
|
|
||||||
use crate::macro_writer::MacroWriter;
|
use crate::macro_writer::MacroWriter;
|
||||||
use crate::x11_safe_wrapper::XDisplay;
|
use crate::x11_safe_wrapper::XDisplay;
|
||||||
|
use crate::{Instructions, Keycode, Position};
|
||||||
|
use std::mem;
|
||||||
|
use std::time;
|
||||||
|
use x11::xlib;
|
||||||
|
use x11::xrecord::{XRecordContext, XRecordInterceptData};
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct EvCallbackData {
|
pub struct EvCallbackData {
|
||||||
pub writer: MacroWriter,
|
pub writer: MacroWriter,
|
||||||
pub xdpy: XDisplay,
|
pub xdpy: XDisplay,
|
||||||
pub recdpy: XDisplay,
|
pub recdpy: XDisplay,
|
||||||
pub ctx: XRecordContext,
|
pub ctx: XRecordContext,
|
||||||
pub working: bool,
|
pub working: bool,
|
||||||
pub last_event: Time,
|
pub last_event: xlib::Time,
|
||||||
pub pos: Position<i16>,
|
pub pos: Position<i16>,
|
||||||
pub stop_key: Keycode,
|
pub stop_key: Keycode,
|
||||||
pub ev_nr: u32,
|
pub ev_nr: u32,
|
||||||
pub max_delay: Option<Time>,
|
pub max_delay: Option<xlib::Time>,
|
||||||
pub no_keypress_yet: bool,
|
pub no_keypress_yet: bool,
|
||||||
pub moving: bool,
|
pub moving: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvCallbackData {
|
impl EvCallbackData {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
writer: MacroWriter,
|
writer: MacroWriter,
|
||||||
xdpy: XDisplay,
|
xdpy: XDisplay,
|
||||||
recdpy: XDisplay,
|
recdpy: XDisplay,
|
||||||
ctx: XRecordContext,
|
ctx: XRecordContext,
|
||||||
stop_key: Keycode,
|
stop_key: Keycode,
|
||||||
pos: Position<i16>,
|
pos: Position<i16>,
|
||||||
max_delay: Option<Time>,
|
max_delay: Option<xlib::Time>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
EvCallbackData {
|
EvCallbackData {
|
||||||
writer,
|
writer,
|
||||||
xdpy,
|
xdpy,
|
||||||
recdpy,
|
recdpy,
|
||||||
ctx,
|
ctx,
|
||||||
stop_key,
|
stop_key,
|
||||||
ev_nr: 0,
|
ev_nr: 0,
|
||||||
working: true,
|
working: true,
|
||||||
pos,
|
pos,
|
||||||
max_delay,
|
max_delay,
|
||||||
no_keypress_yet: true,
|
no_keypress_yet: true,
|
||||||
last_event: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Time,
|
last_event: time::SystemTime::now()
|
||||||
moving: false,
|
.duration_since(time::UNIX_EPOCH)
|
||||||
}
|
.unwrap()
|
||||||
}
|
.as_millis() as xlib::Time,
|
||||||
|
moving: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ptr_is_moving(&self) -> bool { self.moving }
|
pub fn ptr_is_moving(&self) -> bool {
|
||||||
|
self.moving
|
||||||
|
}
|
||||||
|
|
||||||
pub unsafe fn update_pos(&mut self, intercept_data: &mut XRecordInterceptData) -> Position<i16> {
|
pub unsafe fn update_pos(
|
||||||
self.pos.0 = *((intercept_data.data as usize + size_of::<i16>() * 10) as *const i16);
|
&mut self,
|
||||||
self.pos.1 = *((intercept_data.data as usize + size_of::<i16>() * 11) as *const i16);
|
intercept_data: &mut XRecordInterceptData,
|
||||||
self.pos
|
) -> Position<i16> {
|
||||||
}
|
self.pos.0 = *((intercept_data.data as usize + mem::size_of::<i16>() * 10) as *const i16);
|
||||||
|
self.pos.1 = *((intercept_data.data as usize + mem::size_of::<i16>() * 11) as *const i16);
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
|
|
||||||
pub fn write_pos(&mut self) {
|
pub fn write_pos(&mut self) {
|
||||||
self.writer.write(Instructions::MotionNotify(self.pos));
|
self.writer.write(Instructions::MotionNotify(self.pos));
|
||||||
self.moving = false;
|
self.moving = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maybe_write_delay(&mut self, server_time: Time) {
|
pub fn maybe_write_delay(&mut self, server_time: xlib::Time) {
|
||||||
if server_time - self.last_event > 1 {
|
if server_time - self.last_event > 1 {
|
||||||
self.writer.write(Instructions::Delay(calculate_delay(server_time, self.last_event, self.max_delay)));
|
self.writer.write(Instructions::Delay(calculate_delay(
|
||||||
self.last_event = server_time;
|
server_time,
|
||||||
}
|
self.last_event,
|
||||||
}
|
self.max_delay,
|
||||||
|
)));
|
||||||
|
self.last_event = server_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_delay(server_time: Time, last_event: Time, max_delay: Option<Time>) -> Time {
|
fn calculate_delay(
|
||||||
if let Some(max) = max_delay {
|
server_time: xlib::Time,
|
||||||
let max = max as u64;
|
last_event: xlib::Time,
|
||||||
let delay = server_time - last_event;
|
max_delay: Option<xlib::Time>,
|
||||||
|
) -> xlib::Time {
|
||||||
|
if let Some(max) = max_delay {
|
||||||
|
let max = max as u64;
|
||||||
|
let delay = server_time - last_event;
|
||||||
|
|
||||||
if delay > max {
|
if delay > max {
|
||||||
max
|
max
|
||||||
} else {
|
} else {
|
||||||
delay
|
delay
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
server_time - last_event
|
server_time - last_event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
src/lib.rs
20
src/lib.rs
|
@ -7,10 +7,12 @@ use x11::xlib::{ButtonPress, ButtonRelease, KeyPress, KeyRelease, MotionNotify,
|
||||||
|
|
||||||
use crate::x11_safe_wrapper::{Keycode, Keysym};
|
use crate::x11_safe_wrapper::{Keycode, Keysym};
|
||||||
|
|
||||||
pub mod x11_safe_wrapper;
|
pub mod xwrap;
|
||||||
|
|
||||||
pub mod chartbl;
|
pub mod chartbl;
|
||||||
pub mod macro_writer;
|
|
||||||
pub mod ev_callback_data;
|
pub mod ev_callback_data;
|
||||||
|
pub mod macro_writer;
|
||||||
|
pub mod x11_safe_wrapper;
|
||||||
|
|
||||||
pub const KEYPRESS_U8: u8 = KeyPress as u8;
|
pub const KEYPRESS_U8: u8 = KeyPress as u8;
|
||||||
pub const KEYRELEASE_U8: u8 = KeyRelease as u8;
|
pub const KEYRELEASE_U8: u8 = KeyRelease as u8;
|
||||||
|
@ -19,11 +21,11 @@ pub const BUTTONRELEASE_U8: u8 = ButtonRelease as u8;
|
||||||
pub const MOTIONNOTIFY_U8: u8 = MotionNotify as u8;
|
pub const MOTIONNOTIFY_U8: u8 = MotionNotify as u8;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Position<T> (pub T, pub T);
|
pub struct Position<T>(pub T, pub T);
|
||||||
|
|
||||||
impl From<Position<i32>> for Position<i16> {
|
impl From<Position<i32>> for Position<i16> {
|
||||||
fn from(pos: Position<i32>) -> Self {
|
fn from(pos: Position<i32>) -> Self {
|
||||||
Self (pos.0 as i16, pos.1 as i16)
|
Self(pos.0 as i16, pos.1 as i16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +62,9 @@ pub enum Instructions<'a> {
|
||||||
|
|
||||||
impl Display for Instructions<'_> {
|
impl Display for Instructions<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}",
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
match self {
|
match self {
|
||||||
Instructions::Delay(d) => format!("Delay {}", d),
|
Instructions::Delay(d) => format!("Delay {}", d),
|
||||||
Instructions::ButtonPress(b) => format!("ButtonPress {}", b),
|
Instructions::ButtonPress(b) => format!("ButtonPress {}", b),
|
||||||
|
@ -71,8 +75,10 @@ impl Display for Instructions<'_> {
|
||||||
Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks),
|
Instructions::KeySymPress(ks) => format!("KeySymPress {}", ks),
|
||||||
Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
|
Instructions::KeySymRelease(ks) => format!("KeySymRelease {}", ks),
|
||||||
Instructions::KeySym(ks) => format!("KeySym {}", ks),
|
Instructions::KeySym(ks) => format!("KeySym {}", ks),
|
||||||
Instructions::KeyStrPress(kstr) => format!("KeyStrPress {}", kstr.to_str().unwrap()),
|
Instructions::KeyStrPress(kstr) =>
|
||||||
Instructions::KeyStrRelease(kstr) => format!("KeyStrRelease {}", kstr.to_str().unwrap()),
|
format!("KeyStrPress {}", kstr.to_str().unwrap()),
|
||||||
|
Instructions::KeyStrRelease(kstr) =>
|
||||||
|
format!("KeyStrRelease {}", kstr.to_str().unwrap()),
|
||||||
Instructions::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()),
|
Instructions::KeyStr(kstr) => format!("KeyStr {}", kstr.to_str().unwrap()),
|
||||||
Instructions::String(str) => format!("String {}", str),
|
Instructions::String(str) => format!("String {}", str),
|
||||||
Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd),
|
Instructions::ExecBlock(cmd) => format!("ExecBlock {}", cmd),
|
||||||
|
|
|
@ -1,33 +1,29 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::io;
|
|
||||||
use std::io::Write;
|
|
||||||
use crate::Instructions;
|
use crate::Instructions;
|
||||||
|
use std::{fs, io};
|
||||||
|
|
||||||
pub struct MacroWriter {
|
pub struct MacroWriter {
|
||||||
outfile: Box<dyn Write>,
|
outfile: Box<dyn io::Write>,
|
||||||
ignore_delay_capturing: bool,
|
ignore_delay_capturing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacroWriter {
|
impl MacroWriter {
|
||||||
pub fn new(outfile: Option<std::path::PathBuf>, ignore_delay_capturing: bool) -> Self {
|
pub fn new(outfile: Option<std::path::PathBuf>, ignore_delay_capturing: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
outfile: if let Some(outfile) = outfile {
|
outfile: if let Some(outfile) = outfile {
|
||||||
Box::new(File::create(outfile).expect("Failed to create output file"))
|
Box::new(fs::File::create(outfile).expect("Failed to create output file"))
|
||||||
} else {
|
} else {
|
||||||
Box::new(io::stdout())
|
Box::new(io::stdout())
|
||||||
},
|
},
|
||||||
ignore_delay_capturing,
|
ignore_delay_capturing,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(&mut self, instruction: Instructions) {
|
pub fn write(&mut self, instruction: Instructions) {
|
||||||
if self.ignore_delay_capturing {
|
if self.ignore_delay_capturing {
|
||||||
if let Instructions::Delay(_) = instruction {
|
if let Instructions::Delay(_) = instruction {}
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(&mut self.outfile, "{}", instruction).expect("Failed to write instruction to outfile");
|
writeln!(&mut self.outfile, "{}", instruction)
|
||||||
}
|
.expect("Failed to write instruction to outfile");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
use std::{env, slice};
|
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulong};
|
use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulong};
|
||||||
|
use std::{env, slice};
|
||||||
|
|
||||||
use x11::xlib::{Display, GenericEvent, KeyPress, MotionNotify, Time, Window, XAllowEvents, XCloseDisplay, XConvertCase, XDefaultScreen, XEvent, XFlush, XGetKeyboardMapping, XGrabKeyboard, XKeycodeToKeysym, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XQueryPointer, XRootWindow, XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent};
|
use x11::xlib::{
|
||||||
use x11::xrecord::{XRecordAllClients, XRecordClientSpec, XRecordContext, XRecordCreateContext, XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext, XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange};
|
Display, GenericEvent, KeyPress, MotionNotify, Time, Window, XAllowEvents, XCloseDisplay,
|
||||||
|
XConvertCase, XDefaultScreen, XEvent, XFlush, XGetKeyboardMapping, XGrabKeyboard,
|
||||||
|
XKeycodeToKeysym, XKeysymToKeycode, XKeysymToString, XOpenDisplay, XQueryPointer, XRootWindow,
|
||||||
|
XStringToKeysym, XSync, XUngrabKeyboard, XUngrabPointer, XWindowEvent,
|
||||||
|
};
|
||||||
|
use x11::xrecord::{
|
||||||
|
XRecordAllClients, XRecordClientSpec, XRecordContext, XRecordCreateContext,
|
||||||
|
XRecordDisableContext, XRecordEnableContext, XRecordEnableContextAsync, XRecordFreeContext,
|
||||||
|
XRecordInterceptData, XRecordProcessReplies, XRecordQueryVersion, XRecordRange,
|
||||||
|
};
|
||||||
use x11::xtest::{
|
use x11::xtest::{
|
||||||
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
|
XTestFakeButtonEvent, XTestFakeKeyEvent, XTestFakeMotionEvent, XTestGrabControl,
|
||||||
XTestQueryExtension,
|
XTestQueryExtension,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct XDisplay {
|
pub struct XDisplay {
|
||||||
ptr: *mut Display,
|
ptr: *mut Display,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Keysym = c_ulong;
|
pub type Keysym = c_ulong;
|
||||||
|
@ -21,268 +30,266 @@ const FALSE_C: c_int = 0;
|
||||||
const TRUE_C: c_int = 1;
|
const TRUE_C: c_int = 1;
|
||||||
|
|
||||||
impl XDisplay {
|
impl XDisplay {
|
||||||
pub fn open(display_name: Option<String>) -> Self {
|
pub fn open(display_name: Option<String>) -> Self {
|
||||||
let name = CString::new(if let Some(name) = display_name {
|
let name = CString::new(if let Some(name) = display_name {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
env::var("DISPLAY").expect("DISPLAY is not set")
|
env::var("DISPLAY").expect("DISPLAY is not set")
|
||||||
}).unwrap();
|
})
|
||||||
let name_ptr = name.as_bytes().as_ptr();
|
.unwrap();
|
||||||
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
|
let name_ptr = name.as_bytes().as_ptr();
|
||||||
|
let display_ptr = unsafe { XOpenDisplay(name_ptr as *const i8) };
|
||||||
|
|
||||||
Self { ptr: display_ptr }
|
Self { ptr: display_ptr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self) {
|
pub fn close(self) {
|
||||||
unsafe { XCloseDisplay(self.ptr) };
|
unsafe { XCloseDisplay(self.ptr) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync(&self) {
|
pub fn sync(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
XSync(self.ptr, c_int::from(false));
|
XSync(self.ptr, c_int::from(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(&self) {
|
pub fn flush(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
XFlush(self.ptr);
|
XFlush(self.ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_pointer_pos(&self) -> (i32, i32) {
|
pub fn query_pointer_pos(&self) -> (i32, i32) {
|
||||||
let mut r: (i32, i32) = (-1, -1);
|
let mut r: (i32, i32) = (-1, -1);
|
||||||
let mut unneeded_ints: (i32, i32) = (0, 0);
|
let mut unneeded_ints: (i32, i32) = (0, 0);
|
||||||
let mut unneeded_wins: (u64, u64) = (0, 0);
|
let mut unneeded_wins: (u64, u64) = (0, 0);
|
||||||
let mut unneeded_mask: u32 = 0;
|
let mut unneeded_mask: u32 = 0;
|
||||||
unsafe {
|
unsafe {
|
||||||
XQueryPointer(
|
XQueryPointer(
|
||||||
self.ptr,
|
self.ptr,
|
||||||
self.get_root_window(self.get_default_screen()),
|
self.get_root_window(self.get_default_screen()),
|
||||||
&mut unneeded_wins.0,
|
&mut unneeded_wins.0,
|
||||||
&mut unneeded_wins.1,
|
&mut unneeded_wins.1,
|
||||||
&mut r.0,
|
&mut r.0,
|
||||||
&mut r.1,
|
&mut r.1,
|
||||||
&mut unneeded_ints.0,
|
&mut unneeded_ints.0,
|
||||||
&mut unneeded_ints.1,
|
&mut unneeded_ints.1,
|
||||||
&mut unneeded_mask,
|
&mut unneeded_mask,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_default_screen(&self) -> c_int {
|
pub fn get_default_screen(&self) -> c_int {
|
||||||
unsafe { XDefaultScreen(self.ptr) }
|
unsafe { XDefaultScreen(self.ptr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_root_window(&self, screen_nr: c_int) -> Window {
|
pub fn get_root_window(&self, screen_nr: c_int) -> Window {
|
||||||
unsafe { XRootWindow(self.ptr, screen_nr) }
|
unsafe { XRootWindow(self.ptr, screen_nr) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_keyboard_mapping(&self, keycode: Keycode, keycode_count: i32) -> &[Keysym] {
|
pub fn get_keyboard_mapping(&self, keycode: Keycode, keycode_count: i32) -> &[Keysym] {
|
||||||
let mut keysyms_per_keycode = 0;
|
let mut keysyms_per_keycode = 0;
|
||||||
let r = unsafe {
|
let r = unsafe {
|
||||||
let ptr = XGetKeyboardMapping(
|
let ptr = XGetKeyboardMapping(
|
||||||
self.ptr,
|
self.ptr,
|
||||||
keycode as c_uchar,
|
keycode as c_uchar,
|
||||||
keycode_count,
|
keycode_count,
|
||||||
&mut keysyms_per_keycode,
|
&mut keysyms_per_keycode,
|
||||||
);
|
);
|
||||||
|
|
||||||
slice::from_raw_parts::<Keysym>(ptr, keysyms_per_keycode as usize)
|
slice::from_raw_parts::<Keysym>(ptr, keysyms_per_keycode as usize)
|
||||||
};
|
};
|
||||||
|
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_case(&self, keysym: Keysym) -> (Keysym, Keysym) {
|
pub fn convert_case(&self, keysym: Keysym) -> (Keysym, Keysym) {
|
||||||
let mut keysym_lower: Keysym = Keysym::default();
|
let mut keysym_lower: Keysym = Keysym::default();
|
||||||
let mut keysym_upper: Keysym = Keysym::default();
|
let mut keysym_upper: Keysym = Keysym::default();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
XConvertCase(keysym, &mut keysym_lower, &mut keysym_upper);
|
XConvertCase(keysym, &mut keysym_lower, &mut keysym_upper);
|
||||||
}
|
}
|
||||||
|
|
||||||
(keysym_lower, keysym_upper)
|
(keysym_lower, keysym_upper)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keysym_to_keycode(&self, keysym: Keysym) -> Keycode {
|
pub fn keysym_to_keycode(&self, keysym: Keysym) -> Keycode {
|
||||||
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
|
unsafe { XKeysymToKeycode(self.ptr, keysym) as Keycode }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn string_to_keycode(&self, string: &[u8]) -> Keycode {
|
pub fn string_to_keycode(&self, string: &[u8]) -> Keycode {
|
||||||
self.keysym_to_keycode(string_to_keysym(string))
|
self.keysym_to_keycode(string_to_keysym(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym {
|
pub fn keycode_to_keysym(&self, keycode: Keycode) -> Keysym {
|
||||||
unsafe {
|
unsafe { XKeycodeToKeysym(self.ptr, keycode as c_uchar, 0) }
|
||||||
XKeycodeToKeysym(self.ptr, keycode as c_uchar, 0)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn keycode_to_string(&self, keycode: Keycode) -> CString {
|
pub fn keycode_to_string(&self, keycode: Keycode) -> CString {
|
||||||
keysym_to_string(self.keycode_to_keysym(keycode))
|
keysym_to_string(self.keycode_to_keysym(keycode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// XTest stuff
|
// XTest stuff
|
||||||
pub fn has_xtest(&self) -> bool {
|
pub fn has_xtest(&self) -> bool {
|
||||||
let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0);
|
let mut vals: (c_int, c_int, c_int, c_int) = (0, 0, 0, 0);
|
||||||
let has_extension = unsafe {
|
let has_extension = unsafe {
|
||||||
XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3)
|
XTestQueryExtension(self.ptr, &mut vals.0, &mut vals.1, &mut vals.2, &mut vals.3)
|
||||||
};
|
};
|
||||||
has_extension != 0
|
has_extension != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keypress_from_string(&self, string: &[u8], delay: u64) {
|
pub fn send_fake_keypress_from_string(&self, string: &[u8], delay: u64) {
|
||||||
self.send_fake_keypress_from_keysym(string_to_keysym(string), delay)
|
self.send_fake_keypress_from_keysym(string_to_keysym(string), delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keypress_from_keysym(&self, ks: Keysym, delay: u64) {
|
pub fn send_fake_keypress_from_keysym(&self, ks: Keysym, delay: u64) {
|
||||||
self.send_fake_keypress_from_code(self.keysym_to_keycode(ks), delay)
|
self.send_fake_keypress_from_code(self.keysym_to_keycode(ks), delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keypress_from_code(&self, code: Keycode, delay: u64) {
|
pub fn send_fake_keypress_from_code(&self, code: Keycode, delay: u64) {
|
||||||
unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, delay) };
|
unsafe { XTestFakeKeyEvent(self.ptr, code, TRUE_C, delay) };
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_buttonpress(&self, button: u32, delay: u64) {
|
pub fn send_fake_buttonpress(&self, button: u32, delay: u64) {
|
||||||
unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, delay) };
|
unsafe { XTestFakeButtonEvent(self.ptr, button, TRUE_C, delay) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_buttonrelease(&self, button: u32, delay: u64) {
|
pub fn send_fake_buttonrelease(&self, button: u32, delay: u64) {
|
||||||
unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, delay) };
|
unsafe { XTestFakeButtonEvent(self.ptr, button, FALSE_C, delay) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keyrelease_from_string(&self, string: &[u8], delay: u64) {
|
pub fn send_fake_keyrelease_from_string(&self, string: &[u8], delay: u64) {
|
||||||
self.send_fake_keyrelease_from_keysym(string_to_keysym(string), delay)
|
self.send_fake_keyrelease_from_keysym(string_to_keysym(string), delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym, delay: u64) {
|
pub fn send_fake_keyrelease_from_keysym(&self, ks: Keysym, delay: u64) {
|
||||||
self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks), delay)
|
self.send_fake_keyrelease_from_code(self.keysym_to_keycode(ks), delay)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_keyrelease_from_code(&self, code: Keycode, delay: u64) {
|
pub fn send_fake_keyrelease_from_code(&self, code: Keycode, delay: u64) {
|
||||||
unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, delay) };
|
unsafe { XTestFakeKeyEvent(self.ptr, code, FALSE_C, delay) };
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_fake_motion_event(&self, x: i32, y: i32, delay: u64) {
|
pub fn send_fake_motion_event(&self, x: i32, y: i32, delay: u64) {
|
||||||
unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, delay) };
|
unsafe { XTestFakeMotionEvent(self.ptr, -1, x, y, delay) };
|
||||||
self.flush();
|
self.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grab_control(&self) {
|
pub fn grab_control(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
XTestGrabControl(self.ptr, TRUE_C);
|
XTestGrabControl(self.ptr, TRUE_C);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allow_events(&self, event_mode: i32, time: Time) {
|
pub fn allow_events(&self, event_mode: i32, time: Time) {
|
||||||
unsafe { XAllowEvents(self.ptr, event_mode, time) };
|
unsafe { XAllowEvents(self.ptr, event_mode, time) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grab_keyboard(&self, window: u64, owner_events: bool, pointer_mode: i32, keyboard_mode: i32, time: Time) -> i32 {
|
pub fn grab_keyboard(
|
||||||
unsafe {
|
&self,
|
||||||
XGrabKeyboard(
|
window: u64,
|
||||||
self.ptr,
|
owner_events: bool,
|
||||||
window,
|
pointer_mode: i32,
|
||||||
c_int::from(owner_events),
|
keyboard_mode: i32,
|
||||||
pointer_mode,
|
time: Time,
|
||||||
keyboard_mode,
|
) -> i32 {
|
||||||
time,
|
unsafe {
|
||||||
)
|
XGrabKeyboard(
|
||||||
}
|
self.ptr,
|
||||||
}
|
window,
|
||||||
|
c_int::from(owner_events),
|
||||||
|
pointer_mode,
|
||||||
|
keyboard_mode,
|
||||||
|
time,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ungrab_keyboard(&self, time: Time) {
|
pub fn ungrab_keyboard(&self, time: Time) {
|
||||||
unsafe { XUngrabKeyboard(self.ptr, time) };
|
unsafe { XUngrabKeyboard(self.ptr, time) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ungrab_pointer(&self, time: Time) {
|
pub fn ungrab_pointer(&self, time: Time) {
|
||||||
unsafe { XUngrabPointer(self.ptr, time) };
|
unsafe { XUngrabPointer(self.ptr, time) };
|
||||||
}
|
}
|
||||||
pub fn window_event(&self, window: Window, event_mask: i64) -> XEvent {
|
pub fn window_event(&self, window: Window, event_mask: i64) -> XEvent {
|
||||||
// maybe dirty hack to initialize the event var?? idk how else to do this
|
// maybe dirty hack to initialize the event var?? idk how else to do this
|
||||||
let mut r: XEvent = XEvent { type_: GenericEvent };
|
let mut r: XEvent = XEvent {
|
||||||
|
type_: GenericEvent,
|
||||||
|
};
|
||||||
|
|
||||||
unsafe { XWindowEvent(self.ptr, window, event_mask, &mut r); }
|
unsafe {
|
||||||
|
XWindowEvent(self.ptr, window, event_mask, &mut r);
|
||||||
|
}
|
||||||
|
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
// XRecord stuff
|
// XRecord stuff
|
||||||
pub fn has_xrecord(&self) -> bool {
|
pub fn has_xrecord(&self) -> bool {
|
||||||
let mut xrecord_version: (c_int, c_int) = (0, 0);
|
let mut xrecord_version: (c_int, c_int) = (0, 0);
|
||||||
let xrec_res = unsafe { XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1) };
|
let xrec_res = unsafe {
|
||||||
xrec_res == 0
|
XRecordQueryVersion(self.ptr, &mut xrecord_version.0, &mut xrecord_version.1)
|
||||||
}
|
};
|
||||||
|
xrec_res == 0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_record_context(&self, mut protocol_ranges: *mut XRecordRange) -> XRecordContext {
|
pub fn create_record_context(&self, mut protocol_ranges: *mut XRecordRange) -> XRecordContext {
|
||||||
unsafe {
|
unsafe {
|
||||||
(*protocol_ranges).device_events.first = KeyPress as c_uchar;
|
(*protocol_ranges).device_events.first = KeyPress as c_uchar;
|
||||||
(*protocol_ranges).device_events.last = MotionNotify as c_uchar;
|
(*protocol_ranges).device_events.last = MotionNotify as c_uchar;
|
||||||
}
|
}
|
||||||
let mut clients: XRecordClientSpec = XRecordAllClients;
|
let mut clients: XRecordClientSpec = XRecordAllClients;
|
||||||
|
|
||||||
let ctx: XRecordContext = unsafe {
|
let ctx: XRecordContext =
|
||||||
XRecordCreateContext(
|
unsafe { XRecordCreateContext(self.ptr, 0, &mut clients, 1, &mut protocol_ranges, 1) };
|
||||||
self.ptr,
|
ctx
|
||||||
0,
|
}
|
||||||
&mut clients,
|
|
||||||
1,
|
|
||||||
&mut protocol_ranges,
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_context(&self,
|
pub fn enable_context(
|
||||||
ctx: XRecordContext,
|
&self,
|
||||||
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
|
ctx: XRecordContext,
|
||||||
closure: *mut c_char,
|
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
|
||||||
) -> bool {
|
closure: *mut c_char,
|
||||||
unsafe {
|
) -> bool {
|
||||||
XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0
|
unsafe { XRecordEnableContext(self.ptr, ctx, cb, closure as *mut c_char) != 0 }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enable_context_async(&self,
|
pub fn enable_context_async(
|
||||||
ctx: XRecordContext,
|
&self,
|
||||||
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
|
ctx: XRecordContext,
|
||||||
closure: *mut c_char,
|
cb: Option<unsafe extern "C" fn(_: *mut c_char, _: *mut XRecordInterceptData)>,
|
||||||
) -> bool {
|
closure: *mut c_char,
|
||||||
unsafe {
|
) -> bool {
|
||||||
XRecordEnableContextAsync(self.ptr, ctx, cb, closure as *mut c_char) != 0
|
unsafe { XRecordEnableContextAsync(self.ptr, ctx, cb, closure as *mut c_char) != 0 }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn disable_context(&self, ctx: XRecordContext) -> bool {
|
pub fn disable_context(&self, ctx: XRecordContext) -> bool {
|
||||||
unsafe {
|
unsafe { XRecordDisableContext(self.ptr, ctx) != 0 }
|
||||||
XRecordDisableContext(self.ptr, ctx) != 0
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free_context(&self, ctx: XRecordContext) -> bool {
|
pub fn free_context(&self, ctx: XRecordContext) -> bool {
|
||||||
unsafe {
|
unsafe { XRecordFreeContext(self.ptr, ctx) != 0 }
|
||||||
XRecordFreeContext(self.ptr, ctx) != 0
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_replies(&self) {
|
pub fn process_replies(&self) {
|
||||||
unsafe { XRecordProcessReplies(self.ptr) };
|
unsafe { XRecordProcessReplies(self.ptr) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapper for XStringToKeysym. Remember to give a null terminated string!
|
/// Wrapper for XStringToKeysym. Remember to give a null terminated string!
|
||||||
pub fn string_to_keysym(string: &[u8]) -> Keysym {
|
pub fn string_to_keysym(string: &[u8]) -> Keysym {
|
||||||
unsafe { XStringToKeysym(string.as_ptr() as *const c_char) }
|
unsafe { XStringToKeysym(string.as_ptr() as *const c_char) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keysym_to_string(keysym: Keysym) -> CString {
|
pub fn keysym_to_string(keysym: Keysym) -> CString {
|
||||||
unsafe {
|
unsafe {
|
||||||
let cstr = CStr::from_ptr(XKeysymToString(keysym));
|
let cstr = CStr::from_ptr(XKeysymToString(keysym));
|
||||||
CString::from(cstr)
|
CString::from(cstr)
|
||||||
}
|
}
|
||||||
}
|
}
|
226
src/xwrap/display.rs
Normal file
226
src/xwrap/display.rs
Normal 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
62
src/xwrap/error.rs
Normal 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
166
src/xwrap/event.rs
Normal 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
39
src/xwrap/key.rs
Normal 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
6
src/xwrap/mod.rs
Normal 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
15
src/xwrap/screen.rs
Normal 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
5
src/xwrap/window.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
use x11::xlib;
|
||||||
|
|
||||||
|
pub struct Window {
|
||||||
|
pub(super) wid: xlib::Window,
|
||||||
|
}
|
Loading…
Reference in a new issue