diff --git a/.gitignore b/.gitignore index 187b750..63df91c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target .idea/ +*.zip diff --git a/Cargo.toml b/Cargo.toml index 187d0f0..fb36c3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ toml = "0.5.3" serde = { version = "1.0.101", features = ["derive"] } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser"] } +winapi = { version = "0.3", features = ["winuser", "wincon"] } lazy_static = "1.4.0" [target.'cfg(target_os="linux")'.dependencies] diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 1ebde7b..4430cc6 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -1,11 +1,13 @@ +#![windows_subsystem = "windows"] use crate::*; use std::env; use std::process::exit; use getopts::Options; +use std::fs::File; -use winapi::um::winuser::{KBDLLHOOKSTRUCT, WH_KEYBOARD_LL, MSG, GetMessageW, CallNextHookEx, SetWindowsHookExW, INPUT_KEYBOARD, MapVirtualKeyW, LPINPUT, INPUT, KEYBDINPUT, SendInput, KEYEVENTF_SCANCODE, KEYEVENTF_KEYUP, WM_KEYUP, WM_KEYDOWN, MAPVK_VK_TO_VSC}; +use winapi::um::winuser::{KBDLLHOOKSTRUCT, WH_KEYBOARD_LL, MSG, GetMessageW, CallNextHookEx, SetWindowsHookExW, INPUT_KEYBOARD, MapVirtualKeyW, LPINPUT, INPUT, KEYBDINPUT, SendInput, KEYEVENTF_SCANCODE, KEYEVENTF_KEYUP, WM_KEYUP, WM_KEYDOWN, MAPVK_VK_TO_VSC, ShowWindow, SW_HIDE}; use winapi::shared::windef::{HHOOK__, HWND}; use winapi::shared::minwindef::{LRESULT, WPARAM, LPARAM, HINSTANCE}; use winapi::shared::basetsd::ULONG_PTR; @@ -18,8 +20,13 @@ use std::mem::{zeroed, size_of}; use winapi::_core::ptr::null_mut; use winapi::_core::mem::transmute_copy; +use lazy_static::lazy_static; +use std::sync::Mutex; +use winapi::um::wincon::GetConsoleWindow; + type WindowsKeyMaps = KeyMaps; +// this is used for identifying the fake keypresses we insert, so we don't process them in an infinite loop const FAKE_EXTRA_INFO: ULONG_PTR = 332; // non-zero means don't send on, I think https://msdn.microsoft.com/en-us/library/windows/desktop/ms644984(v=vs.85).aspx @@ -90,36 +97,23 @@ impl Keyboard for Device { } } -unsafe impl Sync for WindowsKeyMaps { - // this isn't safe, but windows promises us keybd_proc will only be called by a single thread at - // a time, so if that holds true, this is safe -} - unsafe impl Send for WindowsKeyMaps { - + // windows promises us keybd_proc will only be called by a single thread at a time + // but rust makes us wrap in mutex anyway, so we are extra safe... } const DEVICE: Device = Device; -//static mut KEY_MAPPER_PTR: *const Option = &None; -//static mut KEY_MAPPERe: WindowsKeyMaps = unsafe { std::mem::uninitialized() }; -//static mut KEY_MAPPER: Option = None; -//const KEY_MAPPER_PTR: i32 = 0; -/* -*/ -use lazy_static::lazy_static; -use std::sync::Mutex; - lazy_static! { static ref KEY_MAPPER: Mutex = { let config = parse_args(); - println!("Config: {:?}", config); + //println!("Config: {:?}", config); let key_map = key_map(); //println!("key_map: {:?}", key_map); - println!("config_file: {}", config.config_file); + println!("chosen config file: {}", config.config_file); Mutex::new(WindowsKeyMaps::from_cfg(&key_map, &config.config_file)) }; @@ -127,14 +121,16 @@ static ref KEY_MAPPER: Mutex = { unsafe extern "system" fn keybd_proc(code: c_int, w_param: WPARAM, l_param: LPARAM) -> LRESULT { let kb_struct = *(l_param as *const KBDLLHOOKSTRUCT); - println!("value: {:X}, key: {:X}", w_param, kb_struct.vkCode); + //println!("value: {:X}, key: {:X}", w_param, kb_struct.vkCode); if kb_struct.dwExtraInfo == FAKE_EXTRA_INFO { return CallNextHookEx(null_mut(), code, w_param, l_param); } + /* println!("code: {}, w_param: {}, vkCode: {}, scanCode: {}, flags: {}, time: {}, dwExtraInfo: {}", code, w_param, kb_struct.vkCode, kb_struct.scanCode, kb_struct.flags, kb_struct.time, kb_struct.dwExtraInfo); + */ let mut input_event = InputEvent{ code, @@ -178,35 +174,28 @@ fn send_keybd_input(flags: u32, key_code: USizeableDWORD) { pub fn main_res() -> Result<()> { - /* - let config = parse_args(); - println!("Config: {:?}", config); - - let key_map = key_map(); - //println!("key_map: {:?}", key_map); - - println!("caps_lock_code: {}", DEVICE.caps_lock_code().0); - - let key_map = WindowsKeyMaps::from_cfg(&key_map, &config.config_file); - */ - //let mut ptr: *const WindowsKeyMaps = &key_map; - //let key_map = Box::new(key_map); - //let static_ref = Box::leak(key_map); - //let raw = Box::into_raw(key_map); - //let ptr: *const i32 = &raw; - //let my_num_ptr: *const i32 = &*key_map; - //key_map.leak(); - //KEY_MAPPER_PTR = Box::into_raw(key_map); - //unsafe { KEY_MAPPER = Some(key_map); } - //unsafe { KEY_MAPPER_PTR = &Some(key_map); } - //let bla = unsafe {*KEY_MAPPER_PTR}; + // this is just to cause the lazy_static init to run first, so if -h or -v is wanted, we do that + // and exit immediately... todo: how to avoid mutex/lazy_static entirely??? + let _ = KEY_MAPPER.lock().unwrap(); // now start actually intercepting keypresses let keybd_hhook: AtomicPtr = AtomicPtr::default(); set_hook(WH_KEYBOARD_LL, &keybd_hhook, keybd_proc); + + println!("rusty-keys {} keyboard hook registered, now for some reason you *sometimes* have to type in this window once to activate it, thanks windows!", VERSION); + + unsafe { + // hide window + // todo: probably should be tray icon someplace in future to quit, and error messages as windows popups etc... + let hwnd = GetConsoleWindow(); + ShowWindow( hwnd, SW_HIDE ); + } + let mut msg: MSG = unsafe { zeroed() }; unsafe { GetMessageW(&mut msg, 0 as HWND, 0, 0) }; - + + //std::thread::sleep(std::time::Duration::from_millis(400000)); + Ok(()) } @@ -221,23 +210,41 @@ impl Config { } } +fn get_env_push(key: &str, to_push: &str, vec: &mut Vec) { + if let Some(var) = env::var_os(key) { + if let Ok(str) = var.into_string() { + let mut str = str.to_owned(); + str.push_str(to_push); + vec.push(str); + } + } +} + fn parse_args() -> Config { fn print_usage(program: &str, opts: Options) { - let brief = format!("Usage: {} [options]", program); + let brief = format!("Usage: {} [options] [keymap.toml]", program); println!("{}", opts.usage(&brief)); } let args: Vec<_> = env::args().collect(); + + let mut default_configs = Vec::new(); + get_env_push("USERPROFILE", "\\keymap.toml", &mut default_configs); + get_env_push("APPDATA", "\\keymap.toml", &mut default_configs); + + default_configs.push("keymap.toml".to_string()); + + let c_msg = format!("specify the keymap config file to use (default in order: {:?})", default_configs); let mut opts = Options::new(); opts.optflag("h", "help", "prints this help message"); opts.optflag("v", "version", "prints the version"); - opts.optopt("c", "config", "specify the keymap config file to use (default: /etc/rusty-keys/keymap.toml)", "FILE"); + opts.optopt("c", "config", &c_msg, "FILE"); let matches = opts.parse(&args[1..]); if matches.is_err() { print_usage(&args[0], opts); - exit(0); + exit(1); } let matches = matches.unwrap(); if matches.opt_present("h") { @@ -250,7 +257,21 @@ fn parse_args() -> Config { exit(0); } - let config_file = matches.opt_str("c").unwrap_or("/etc/rusty-keys/keymap.toml".to_owned()); + let config_file = matches.opt_str("c").unwrap_or_else(|| { + let remaining_args = matches.free; + if remaining_args.len() > 0 { + remaining_args[0].clone() + } else { + for keymap in default_configs.drain(..) { + if File::open(&keymap).is_ok() { + return keymap; + } + } + println!("Error: no keymap.toml found..."); + print_usage(&args[0], opts); + exit(1); + } + }); Config::new(config_file) } diff --git a/win.sh b/win.sh new file mode 100755 index 0000000..13eb5a5 --- /dev/null +++ b/win.sh @@ -0,0 +1,32 @@ +#!/bin/sh +set -euxo pipefail + +target="${1-x86_64-pc-windows-gnu}" + +cargo build --target "$target" --release + +cp keymap.toml target/"$target"/release/ +cd target/"$target"/release/ +strip rusty-keys.exe +zip -9 rusty-keys-"$target".zip rusty-keys.exe keymap.toml +mv rusty-keys-"$target".zip ../../.. + +exit 0 + +# on arch linux, run these to enable cross compiling to win64: + +pacman -S mingw-w64-gcc + +# you can change these, if you want +CHANNEL=stable +ARCH=x86_64 + +rustup install $CHANNEL-$ARCH-pc-windows-gnu && rustup target add $ARCH-pc-windows-gnu + +for lib in crt2.o dllcrt2.o libmsvcrt.a; do cp -v /usr/x86_64-w64-mingw32/lib/$lib $HOME/.rustup/toolchains/$CHANNEL-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/; done + +cat >> ~/.cargo/config <