diff --git a/Cargo.lock b/Cargo.lock
index 130998e..502a99d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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,8 +74,9 @@ dependencies = [
name = "easymacros"
version = "0.2.0"
dependencies = [
+ "anyhow",
"clap",
- "x11",
+ "xcb",
]
[[package]]
@@ -109,6 +116,12 @@ version = "0.2.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
[[package]]
name = "once_cell"
version = "1.12.0"
@@ -121,12 +134,6 @@ version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
-[[package]]
-name = "pkg-config"
-version = "0.3.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
-
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -160,6 +167,15 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "quick-xml"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.18"
@@ -245,11 +261,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
-name = "x11"
-version = "2.19.1"
+name = "xcb"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6dd0565fa8bfba8c5efe02725b14dff114c866724eff2cfd44d76cea74bcd87a"
+checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9"
dependencies = [
+ "bitflags",
"libc",
- "pkg-config",
+ "quick-xml",
]
diff --git a/Cargo.toml b/Cargo.toml
index c65e9e7..8d0f48d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,5 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
-clap = { version = "3.2.4", features = ["derive"] }
\ No newline at end of file
+anyhow = "1.0.65"
+xcb = { version = "1.1.1", features = [ "xtest", "record" ]}
+clap = { version = "3.2.4", features = ["derive"] }
diff --git a/README.md b/README.md
index acf0e1f..2c4eb65 100644
--- a/README.md
+++ b/README.md
@@ -10,35 +10,34 @@ This program is inspired by [**xmacro**](https://github.com/Ortega-Dan/xmacroInc
:pen_ballpoint: TODOs :notepad_spiral:
- [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
#
diff --git a/src/bin/easymacroplay.rs b/src/bin/easymacroplay.rs
deleted file mode 100644
index ba1960a..0000000
--- a/src/bin/easymacroplay.rs
+++ /dev/null
@@ -1,146 +0,0 @@
-use clap::Parser;
-use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay};
-use std::ffi::CString;
-use std::process::{Command, exit};
-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,
- /// Display to run the macro on. This uses the $DISPLAY environment variable by default.
- #[clap(short = 'D', long)]
- display: Option,
- /// Delay for events to be sent.
- #[clap(short, long)]
- delay: Option,
-}
-
-fn main() {
- let args = Args::parse();
-
- let display = get_remote(args.display);
- let delay = args.delay.unwrap_or(10);
-
- if let Some(input_file_path) = args.input_file {
- let input_file_contents = fs::read_to_string(input_file_path).expect("Couldn't read macro file");
-
- for instruction in input_file_contents.lines() {
- run_instruction(instruction, &display, delay);
- }
- } else {
- println!("No input file specified, reading from stdin.");
- let stdin = stdin();
-
- loop {
- let mut line = String::new();
- stdin.read_line(&mut line).expect("Couldn't read line from stdin");
- // Without this it crashes because apparently it doesn't properly read the next input line?
- println!();
- line = line.trim().to_string();
- run_instruction(&*line, &display, delay);
- }
- }
-
- display.close();
-}
-
-fn get_remote(display_name: Option) -> XDisplay {
- let display = XDisplay::open(display_name);
-
- if !display.has_xtest() {
- eprintln!("XTest not supported!");
- display.close();
- exit(1)
- }
-
- display.grab_control();
- display.sync();
-
- display
-}
-
-fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
- let instruction_split: Vec<&str> = instruction.split(' ').collect();
-
- match instruction_split[0] {
- "Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
- "ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
- "ButtonRelease" => dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay),
- "MotionNotify" => dpy.send_fake_motion_event(instruction_split[1].parse().unwrap(), instruction_split[2].parse().unwrap(), delay),
- "KeyCodePress" => dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay),
- "KeyCodeRelease" => dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay),
- "KeySymPress" => dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay),
- "KeySymRelease" => dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay),
-
- "KeySym" => {
- let key: Keysym = instruction_split[1].parse().unwrap();
- dpy.send_fake_keypress_from_keysym(key, delay);
- dpy.send_fake_keyrelease_from_keysym(key, delay);
- }
- "KeyStrPress" => dpy.send_fake_keypress_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
- "KeyStrRelease" => dpy.send_fake_keyrelease_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
- "KeyStr" => {
- let keystring = CString::new(instruction_split[1]).unwrap();
- dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
- dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
- }
- "String" => {
- for c in instruction["String".len() + 1..].chars() {
- send_char(dpy, c, delay);
- }
- }
- "ExecBlock" | "ExecNoBlock" => {
- let mut command = Command::new(instruction_split[1]);
- for arg in &instruction_split[2..] {
- command.arg(arg);
- }
- if instruction_split[0] == "ExecBlock" {
- command.status().unwrap();
- } else {
- command.spawn().unwrap();
- }
- }
- c => panic!("Unknown command {:?}", instruction_split),
- }
-}
-
-fn send_char(dpy: &XDisplay, c: char, delay: u64) {
- // get keystring from character and turn it into a keysym
- let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
- let keycode = dpy.keysym_to_keycode(keysym);
-
- if keycode == 0 {
- eprintln!("No keycode found for character '{}'", c);
- return;
- }
-
- let map_ks = dpy.get_keyboard_mapping(keycode, 1);
- if map_ks[0] == 0 {
- eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
- return;
- }
-
- let (ks_lower, ks_upper) = dpy.convert_case(keysym);
-
- // check if shift has to be pressed as well
- let mut shift_needed = true;
- if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
- shift_needed = false;
- }
- if keysym == ks_lower && keysym != ks_upper {
- shift_needed = false;
- }
-
- if shift_needed { dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); }
- dpy.send_fake_keypress_from_code(keycode, delay);
- dpy.send_fake_keyrelease_from_code(keycode, delay);
- if shift_needed { dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); }
-}
\ No newline at end of file
diff --git a/src/bin/easymacrorec.rs b/src/bin/easymacrorec.rs
deleted file mode 100644
index 9d91d88..0000000
--- a/src/bin/easymacrorec.rs
+++ /dev/null
@@ -1,189 +0,0 @@
-extern crate core;
-
-use std::ffi::c_void;
-use std::os::raw::{c_char};
-use std::process::{exit};
-use std::ptr::addr_of;
-
-use clap::Parser;
-use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, XKeyEvent};
-use x11::xrecord::{XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData};
-
-use easymacros::{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};
-
-/// 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,
- /// Display to run the macro on. This uses the $DISPLAY environment variable by default.
- #[clap(short = 'D', long)]
- display: Option,
- /// Max Delay in milliseconds for macro delays
- #[clap(short, long)]
- max_delay: Option,
- /// Allow delay capturing in recording output. If this flag is set, the program will ignore the max_delay.
- #[clap(short, long)]
- ignore_delay_capturing: bool,
-}
-
-fn main() {
- let args = Args::parse();
-
- let display = XDisplay::open(args.display.clone());
- let recorded_display = XDisplay::open(args.display.clone());
- let stop_key = get_stop_key(display);
- let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
-
- event_loop(
- display,
- recorded_display,
- stop_key,
- writer,
- args.max_delay,
- );
-
- display.close();
-}
-
-fn get_stop_key(display: XDisplay) -> Keycode {
- let screen = display.get_default_screen();
-
- let root = display.get_root_window(screen);
- let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
-
- if potential_err != GrabSuccess {
- eprintln!("Couldn't grab keyboard!");
- exit(1);
- }
-
- println!("Press the key you want to use to stop recording the macro.");
-
- let stop_key;
- loop {
- display.allow_events(SyncPointer, CurrentTime);
- let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
- stop_key = ev.keycode;
- break;
- }
-
- display.ungrab_keyboard(CurrentTime);
- display.ungrab_pointer(CurrentTime);
-
- stop_key
-}
-
-fn event_loop(
- xdpy: XDisplay,
- recdpy: XDisplay,
- stop_key: Keycode,
- mut writer: MacroWriter,
- max_delay: Option