diff --git a/Cargo.lock b/Cargo.lock
index 130998e..9fde414 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,6 +74,7 @@ dependencies = [
name = "easymacros"
version = "0.2.0"
dependencies = [
+ "anyhow",
"clap",
"x11",
]
diff --git a/Cargo.toml b/Cargo.toml
index c65e9e7..be5aee0 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]
+anyhow = "1.0.65"
x11 = { version = "2.19.1", features = ["xlib", "xtest"] }
-clap = { version = "3.2.4", features = ["derive"] }
\ No newline at end of file
+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
index ba1960a..386c2cc 100644
--- a/src/bin/easymacroplay.rs
+++ b/src/bin/easymacroplay.rs
@@ -1,146 +1,174 @@
use clap::Parser;
-use easymacros::x11_safe_wrapper::{Keysym, string_to_keysym, XDisplay};
+use easymacros::chartbl::CHARTBL;
+use easymacros::x11_safe_wrapper::{string_to_keysym, Keysym, XDisplay};
use std::ffi::CString;
-use std::process::{Command, exit};
+use std::io::stdin;
+use std::process::{exit, Command};
use std::time::Duration;
use std::{fs, thread};
-use std::io::stdin;
use x11::keysym::XK_Shift_L;
-use easymacros::chartbl::CHARTBL;
/// Macro player module for easymacros. It's compatible with xmacro macros.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
- /// The file that contains the macro to run.
- #[clap(value_parser, value_name = "input_file", value_hint = clap::ValueHint::FilePath)]
- input_file: Option,
- /// 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,
+ /// 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 args = Args::parse();
- let display = get_remote(args.display);
- let delay = args.delay.unwrap_or(10);
+ let display = get_remote(args.display);
+ let delay = args.delay.unwrap_or(10);
- if let Some(input_file_path) = args.input_file {
- let input_file_contents = fs::read_to_string(input_file_path).expect("Couldn't read macro file");
+ if let Some(input_file_path) = args.input_file {
+ let input_file_contents =
+ fs::read_to_string(input_file_path).expect("Couldn't read macro file");
- for instruction in input_file_contents.lines() {
- run_instruction(instruction, &display, delay);
- }
- } else {
- println!("No input file specified, reading from stdin.");
- let stdin = stdin();
+ for instruction in input_file_contents.lines() {
+ run_instruction(instruction, &display, delay);
+ }
+ } else {
+ println!("No input file specified, reading from stdin.");
+ let stdin = stdin();
- loop {
- let mut line = String::new();
- stdin.read_line(&mut line).expect("Couldn't read line from stdin");
- // Without this it crashes because apparently it doesn't properly read the next input line?
- println!();
- line = line.trim().to_string();
- run_instruction(&*line, &display, delay);
- }
- }
+ loop {
+ // TODO: Unify with macro_writer using trait objects
+ let mut line = String::new();
+ stdin
+ .read_line(&mut line)
+ .expect("Couldn't read line from stdin");
+ // Without this it crashes because apparently it doesn't properly read the next input line?
+ println!();
+ line = line.trim().to_string();
+ run_instruction(&*line, &display, delay);
+ }
+ }
- display.close();
+ display.close();
}
fn get_remote(display_name: Option) -> XDisplay {
- let display = XDisplay::open(display_name);
+ let display = XDisplay::open(display_name);
- if !display.has_xtest() {
- eprintln!("XTest not supported!");
- display.close();
- exit(1)
- }
+ if !display.has_xtest() {
+ eprintln!("XTest not supported!");
+ display.close();
+ exit(1)
+ }
- display.grab_control();
- display.sync();
+ display.grab_control();
+ display.sync();
- display
+ display
}
fn run_instruction(instruction: &str, dpy: &XDisplay, delay: u64) {
- let instruction_split: Vec<&str> = instruction.split(' ').collect();
+ let instruction_split: Vec<&str> = instruction.split(' ').collect();
- match instruction_split[0] {
- "Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
- "ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
- "ButtonRelease" => dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay),
- "MotionNotify" => dpy.send_fake_motion_event(instruction_split[1].parse().unwrap(), instruction_split[2].parse().unwrap(), delay),
- "KeyCodePress" => dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay),
- "KeyCodeRelease" => dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay),
- "KeySymPress" => dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay),
- "KeySymRelease" => dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay),
+ match instruction_split[0] {
+ "Delay" => thread::sleep(Duration::from_millis(instruction_split[1].parse().unwrap())),
+ "ButtonPress" => dpy.send_fake_buttonpress(instruction_split[1].parse().unwrap(), delay),
+ "ButtonRelease" => {
+ dpy.send_fake_buttonrelease(instruction_split[1].parse().unwrap(), delay)
+ }
+ "MotionNotify" => dpy.send_fake_motion_event(
+ instruction_split[1].parse().unwrap(),
+ instruction_split[2].parse().unwrap(),
+ delay,
+ ),
+ "KeyCodePress" => {
+ dpy.send_fake_keypress_from_code(instruction_split[1].parse().unwrap(), delay)
+ }
+ "KeyCodeRelease" => {
+ dpy.send_fake_keyrelease_from_code(instruction_split[1].parse().unwrap(), delay)
+ }
+ "KeySymPress" => {
+ dpy.send_fake_keypress_from_keysym(instruction_split[1].parse().unwrap(), delay)
+ }
+ "KeySymRelease" => {
+ dpy.send_fake_keyrelease_from_keysym(instruction_split[1].parse().unwrap(), delay)
+ }
- "KeySym" => {
- let key: Keysym = instruction_split[1].parse().unwrap();
- dpy.send_fake_keypress_from_keysym(key, delay);
- dpy.send_fake_keyrelease_from_keysym(key, delay);
- }
- "KeyStrPress" => dpy.send_fake_keypress_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
- "KeyStrRelease" => dpy.send_fake_keyrelease_from_string(CString::new(instruction_split[1]).unwrap().as_bytes(), delay),
- "KeyStr" => {
- let keystring = CString::new(instruction_split[1]).unwrap();
- dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
- dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
- }
- "String" => {
- for c in instruction["String".len() + 1..].chars() {
- send_char(dpy, c, delay);
- }
- }
- "ExecBlock" | "ExecNoBlock" => {
- let mut command = Command::new(instruction_split[1]);
- for arg in &instruction_split[2..] {
- command.arg(arg);
- }
- if instruction_split[0] == "ExecBlock" {
- command.status().unwrap();
- } else {
- command.spawn().unwrap();
- }
- }
- c => panic!("Unknown command {:?}", instruction_split),
- }
+ "KeySym" => {
+ let key: Keysym = instruction_split[1].parse().unwrap();
+ dpy.send_fake_keypress_from_keysym(key, delay);
+ dpy.send_fake_keyrelease_from_keysym(key, delay);
+ }
+ "KeyStrPress" => dpy.send_fake_keypress_from_string(
+ CString::new(instruction_split[1]).unwrap().as_bytes(),
+ delay,
+ ),
+ "KeyStrRelease" => dpy.send_fake_keyrelease_from_string(
+ CString::new(instruction_split[1]).unwrap().as_bytes(),
+ delay,
+ ),
+ "KeyStr" => {
+ let keystring = CString::new(instruction_split[1]).unwrap();
+ dpy.send_fake_keypress_from_string(keystring.as_bytes(), delay);
+ dpy.send_fake_keyrelease_from_string(keystring.as_bytes(), delay);
+ }
+ "String" => {
+ for c in instruction["String".len() + 1..].chars() {
+ send_char(dpy, c, delay);
+ }
+ }
+ "ExecBlock" | "ExecNoBlock" => {
+ let mut command = Command::new(instruction_split[1]);
+ for arg in &instruction_split[2..] {
+ command.arg(arg);
+ }
+ if instruction_split[0] == "ExecBlock" {
+ command.status().unwrap();
+ } else {
+ command.spawn().unwrap();
+ }
+ }
+ c => panic!("Unknown command {:?}", instruction_split),
+ }
}
fn send_char(dpy: &XDisplay, c: char, delay: u64) {
- // get keystring from character and turn it into a keysym
- let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
- let keycode = dpy.keysym_to_keycode(keysym);
+ // get keystring from character and turn it into a keysym
+ let keysym = string_to_keysym(CHARTBL[c as usize].as_ref());
+ let keycode = dpy.keysym_to_keycode(keysym);
- if keycode == 0 {
- eprintln!("No keycode found for character '{}'", c);
- return;
- }
+ if keycode == 0 {
+ eprintln!("No keycode found for character '{}'", c);
+ return;
+ }
- let map_ks = dpy.get_keyboard_mapping(keycode, 1);
- if map_ks[0] == 0 {
- eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
- return;
- }
+ let map_ks = dpy.get_keyboard_mapping(keycode, 1);
+ if map_ks[0] == 0 {
+ eprintln!("XGetKeyboardMapping failed (keycode: {})", keycode);
+ return;
+ }
- let (ks_lower, ks_upper) = dpy.convert_case(keysym);
+ let (ks_lower, ks_upper) = dpy.convert_case(keysym);
- // check if shift has to be pressed as well
- let mut shift_needed = true;
- if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
- shift_needed = false;
- }
- if keysym == ks_lower && keysym != ks_upper {
- shift_needed = false;
- }
+ // check if shift has to be pressed as well
+ let mut shift_needed = true;
+ if keysym == map_ks[0] && (keysym == ks_lower && keysym == ks_upper) {
+ shift_needed = false;
+ }
+ if keysym == ks_lower && keysym != ks_upper {
+ shift_needed = false;
+ }
- if shift_needed { dpy.send_fake_keypress_from_keysym(XK_Shift_L as Keysym, delay); }
- dpy.send_fake_keypress_from_code(keycode, delay);
- dpy.send_fake_keyrelease_from_code(keycode, delay);
- if shift_needed { dpy.send_fake_keyrelease_from_keysym(XK_Shift_L as Keysym, delay); }
-}
\ No newline at end of file
+ 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);
+ }
+}
diff --git a/src/bin/easymacrorec.rs b/src/bin/easymacrorec.rs
index 9d91d88..9107599 100644
--- a/src/bin/easymacrorec.rs
+++ b/src/bin/easymacrorec.rs
@@ -1,189 +1,198 @@
extern crate core;
use std::ffi::c_void;
-use std::os::raw::{c_char};
-use std::process::{exit};
+use std::os::raw::c_char;
+use std::process::exit;
use std::ptr::addr_of;
use clap::Parser;
-use x11::xlib::{CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree, XKeyEvent};
-use x11::xrecord::{XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData};
+use x11::xlib::{
+ CurrentTime, GrabModeAsync, GrabModeSync, GrabSuccess, KeyPressMask, SyncPointer, Time, XFree,
+ XKeyEvent,
+};
+use x11::xrecord::{
+ XRecordAllocRange, XRecordEndOfData, XRecordFreeData, XRecordInterceptData, XRecordStartOfData,
+};
-use easymacros::{BUTTONPRESS_U8, BUTTONRELEASE_U8, Instructions, KEYPRESS_U8, KEYRELEASE_U8, MOTIONNOTIFY_U8, Position};
use easymacros::ev_callback_data::EvCallbackData;
use easymacros::macro_writer::MacroWriter;
use easymacros::x11_safe_wrapper::{Keycode, XDisplay};
+use easymacros::{
+ Instructions, Position, BUTTONPRESS_U8, BUTTONRELEASE_U8, KEYPRESS_U8, KEYRELEASE_U8,
+ MOTIONNOTIFY_U8,
+};
/// Macro recording module for easymacros. Outputs are partially compatible with xmacro.
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
- /// The file to record the macro to. Defaults to writing to stdout.
- #[clap(value_parser, value_name = "output_file", value_hint = clap::ValueHint::FilePath)]
- output_file: Option,
- /// 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,
+ /// 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 args = Args::parse();
- let display = XDisplay::open(args.display.clone());
- let recorded_display = XDisplay::open(args.display.clone());
- let stop_key = get_stop_key(display);
- let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
+ let display = XDisplay::open(args.display.clone());
+ let recorded_display = XDisplay::open(args.display.clone());
+ let stop_key = get_stop_key(display);
+ let writer = MacroWriter::new(args.output_file, args.ignore_delay_capturing);
- event_loop(
- display,
- recorded_display,
- stop_key,
- writer,
- args.max_delay,
- );
+ event_loop(display, recorded_display, stop_key, writer, args.max_delay);
- display.close();
+ display.close();
}
fn get_stop_key(display: XDisplay) -> Keycode {
- let screen = display.get_default_screen();
+ let screen = display.get_default_screen();
- let root = display.get_root_window(screen);
- let potential_err = display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
+ let root = display.get_root_window(screen);
+ let potential_err =
+ display.grab_keyboard(root, false, GrabModeSync, GrabModeAsync, CurrentTime);
- if potential_err != GrabSuccess {
- eprintln!("Couldn't grab keyboard!");
- exit(1);
- }
+ if potential_err != GrabSuccess {
+ eprintln!("Couldn't grab keyboard!");
+ exit(1);
+ }
- println!("Press the key you want to use to stop recording the macro.");
+ println!("Press the key you want to use to stop recording the macro.");
- let stop_key;
- loop {
- display.allow_events(SyncPointer, CurrentTime);
- let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
- stop_key = ev.keycode;
- break;
- }
+ let stop_key;
+ loop {
+ display.allow_events(SyncPointer, CurrentTime);
+ let ev = XKeyEvent::from(display.window_event(root, KeyPressMask));
+ stop_key = ev.keycode;
+ break;
+ }
- display.ungrab_keyboard(CurrentTime);
- display.ungrab_pointer(CurrentTime);
+ display.ungrab_keyboard(CurrentTime);
+ display.ungrab_pointer(CurrentTime);
- stop_key
+ stop_key
}
fn event_loop(
- xdpy: XDisplay,
- recdpy: XDisplay,
- stop_key: Keycode,
- mut writer: MacroWriter,
- max_delay: Option