2019-10-07 01:59:43 -04:00
#![ windows_subsystem = " windows " ]
2019-10-07 01:24:29 -04:00
use crate ::* ;
use std ::env ;
use std ::process ::exit ;
use getopts ::Options ;
2019-10-07 01:59:43 -04:00
use std ::fs ::File ;
2019-10-07 01:24:29 -04:00
2019-10-07 01:59:43 -04:00
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 } ;
2019-10-07 01:24:29 -04:00
use winapi ::shared ::windef ::{ HHOOK__ , HWND } ;
use winapi ::shared ::minwindef ::{ LRESULT , WPARAM , LPARAM , HINSTANCE } ;
use winapi ::shared ::basetsd ::ULONG_PTR ;
use winapi ::ctypes ::c_int ;
pub mod codes ;
use codes ::* ;
use std ::sync ::atomic ::{ AtomicPtr , Ordering } ;
use std ::mem ::{ zeroed , size_of } ;
use winapi ::_core ::ptr ::null_mut ;
use winapi ::_core ::mem ::transmute_copy ;
2019-10-07 01:59:43 -04:00
use lazy_static ::lazy_static ;
use std ::sync ::Mutex ;
use winapi ::um ::wincon ::GetConsoleWindow ;
2019-10-07 01:24:29 -04:00
type WindowsKeyMaps = KeyMaps < Device , USizeableDWORD , InputEvent , LRESULT > ;
2019-10-07 01:59:43 -04:00
// this is used for identifying the fake keypresses we insert, so we don't process them in an infinite loop
2019-10-07 01:24:29 -04:00
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
const BLOCK_KEY : LRESULT = 1 ;
struct InputEvent {
code : c_int ,
value : WPARAM ,
kb_hook_pointer : LPARAM ,
kb_hook_struct : KBDLLHOOKSTRUCT ,
}
impl KeyEvent < USizeableDWORD > for InputEvent {
fn code ( & self ) -> USizeableDWORD {
USizeableDWORD ( self . kb_hook_struct . vkCode )
}
fn value ( & self ) -> KeyState {
match self . value as u32 {
WM_KEYUP = > KeyState ::UP ,
WM_KEYDOWN = > KeyState ::DOWN ,
_ = > KeyState ::OTHER ,
}
}
}
struct Device ;
impl Keyboard < USizeableDWORD , InputEvent , LRESULT > for Device {
fn send ( & self , event : & mut InputEvent ) -> Result < LRESULT > {
Ok ( unsafe { CallNextHookEx ( null_mut ( ) , event . code , event . value , event . kb_hook_pointer ) } )
}
fn send_mod_code ( & self , code : USizeableDWORD , event : & mut InputEvent ) -> Result < LRESULT > {
// event.value should only ever be UP/DOWN when this method is called
self . send_mod_code_value ( code , event . value as u32 = = WM_KEYUP , event )
}
fn send_mod_code_value ( & self , code : USizeableDWORD , up_not_down : bool , _event : & mut InputEvent ) -> Result < LRESULT > {
let flags = if up_not_down {
KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP
} else {
KEYEVENTF_SCANCODE
} ;
send_keybd_input ( flags , code ) ;
Ok ( BLOCK_KEY )
}
fn synchronize ( & self ) -> Result < LRESULT > {
// no-op here
Ok ( 0 )
}
fn left_shift_code ( & self ) -> USizeableDWORD {
USizeableDWORD ( KEY_LEFTSHIFT )
}
fn right_shift_code ( & self ) -> USizeableDWORD {
USizeableDWORD ( KEY_RIGHTSHIFT )
}
fn caps_lock_code ( & self ) -> USizeableDWORD {
USizeableDWORD ( KEY_CAPSLOCK )
}
fn block_key ( & self ) -> Result < LRESULT > {
Ok ( BLOCK_KEY )
}
}
unsafe impl Send for WindowsKeyMaps {
2019-10-07 01:59:43 -04:00
// 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...
2019-10-07 01:24:29 -04:00
}
const DEVICE : Device = Device ;
lazy_static! {
static ref KEY_MAPPER : Mutex < WindowsKeyMaps > = {
let config = parse_args ( ) ;
2019-10-07 01:59:43 -04:00
//println!("Config: {:?}", config);
2019-10-07 01:24:29 -04:00
let key_map = key_map ( ) ;
//println!("key_map: {:?}", key_map);
2019-10-07 01:59:43 -04:00
println! ( " chosen config file: {} " , config . config_file ) ;
2019-10-07 01:24:29 -04:00
Mutex ::new ( WindowsKeyMaps ::from_cfg ( & key_map , & config . config_file ) )
} ;
}
unsafe extern " system " fn keybd_proc ( code : c_int , w_param : WPARAM , l_param : LPARAM ) -> LRESULT {
let kb_struct = * ( l_param as * const KBDLLHOOKSTRUCT ) ;
2019-10-07 01:59:43 -04:00
//println!("value: {:X}, key: {:X}", w_param, kb_struct.vkCode);
2019-10-07 01:24:29 -04:00
if kb_struct . dwExtraInfo = = FAKE_EXTRA_INFO {
return CallNextHookEx ( null_mut ( ) , code , w_param , l_param ) ;
}
2019-10-07 01:59:43 -04:00
/*
2019-10-07 01:24:29 -04:00
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 ) ;
2019-10-07 01:59:43 -04:00
* /
2019-10-07 01:24:29 -04:00
let mut input_event = InputEvent {
code ,
value : w_param ,
kb_hook_pointer : l_param ,
kb_hook_struct : kb_struct ,
} ;
// .unwrap() is ok because windows impl can actually never can fail
//DEVICE.send(&mut input_event).unwrap()
//KEY_MAPPER.send_event(&mut input_event, &DEVICE).unwrap()
KEY_MAPPER . lock ( ) . unwrap ( ) . send_event ( & mut input_event , & DEVICE ) . unwrap ( )
}
fn set_hook (
hook_id : i32 ,
hook_ptr : & AtomicPtr < HHOOK__ > ,
hook_proc : unsafe extern " system " fn ( c_int , WPARAM , LPARAM ) -> LRESULT ,
) {
hook_ptr . store (
unsafe { SetWindowsHookExW ( hook_id , Some ( hook_proc ) , 0 as HINSTANCE , 0 ) } ,
Ordering ::Relaxed ,
) ;
}
fn send_keybd_input ( flags : u32 , key_code : USizeableDWORD ) {
let mut input = INPUT {
type_ : INPUT_KEYBOARD ,
u : unsafe {
transmute_copy ( & KEYBDINPUT {
wVk : 0 ,
wScan : MapVirtualKeyW ( key_code . 0 , MAPVK_VK_TO_VSC ) as u16 ,
dwFlags : flags ,
time : 0 ,
dwExtraInfo : FAKE_EXTRA_INFO ,
} )
} ,
} ;
unsafe { SendInput ( 1 , & mut input as LPINPUT , size_of ::< INPUT > ( ) as c_int ) } ;
}
pub fn main_res ( ) -> Result < ( ) > {
2019-10-07 01:59:43 -04:00
// 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 ( ) ;
2019-10-07 01:24:29 -04:00
// now start actually intercepting keypresses
let keybd_hhook : AtomicPtr < HHOOK__ > = AtomicPtr ::default ( ) ;
set_hook ( WH_KEYBOARD_LL , & keybd_hhook , keybd_proc ) ;
2019-10-07 01:59:43 -04:00
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 ) ;
}
2019-10-07 01:24:29 -04:00
let mut msg : MSG = unsafe { zeroed ( ) } ;
unsafe { GetMessageW ( & mut msg , 0 as HWND , 0 , 0 ) } ;
2019-10-07 01:59:43 -04:00
//std::thread::sleep(std::time::Duration::from_millis(400000));
2019-10-07 01:24:29 -04:00
Ok ( ( ) )
}
#[ derive(Debug) ]
struct Config {
config_file : String
}
impl Config {
fn new ( config_file : String ) -> Self {
Config { config_file : config_file }
}
}
2019-10-07 01:59:43 -04:00
fn get_env_push ( key : & str , to_push : & str , vec : & mut Vec < String > ) {
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 ) ;
}
}
}
2019-10-07 01:24:29 -04:00
fn parse_args ( ) -> Config {
fn print_usage ( program : & str , opts : Options ) {
2019-10-07 01:59:43 -04:00
let brief = format! ( " Usage: {} [options] [keymap.toml] " , program ) ;
2019-10-07 01:24:29 -04:00
println! ( " {} " , opts . usage ( & brief ) ) ;
}
let args : Vec < _ > = env ::args ( ) . collect ( ) ;
2019-10-07 01:59:43 -04:00
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 ) ;
2019-10-07 01:24:29 -04:00
let mut opts = Options ::new ( ) ;
opts . optflag ( " h " , " help " , " prints this help message " ) ;
opts . optflag ( " v " , " version " , " prints the version " ) ;
2019-10-07 01:59:43 -04:00
opts . optopt ( " c " , " config " , & c_msg , " FILE " ) ;
2019-10-07 01:24:29 -04:00
let matches = opts . parse ( & args [ 1 .. ] ) ;
if matches . is_err ( ) {
print_usage ( & args [ 0 ] , opts ) ;
2019-10-07 01:59:43 -04:00
exit ( 1 ) ;
2019-10-07 01:24:29 -04:00
}
let matches = matches . unwrap ( ) ;
if matches . opt_present ( " h " ) {
print_usage ( & args [ 0 ] , opts ) ;
exit ( 0 ) ;
}
if matches . opt_present ( " v " ) {
println! ( " rusty-keys {} " , VERSION ) ;
exit ( 0 ) ;
}
2019-10-07 01:59:43 -04:00
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 ) ;
}
} ) ;
2019-10-07 01:24:29 -04:00
Config ::new ( config_file )
}