Skip to content

Commit cc9de01

Browse files
authored
feat: keyboard-layout independent key bindings (#169)
1 parent 2fde6ec commit cc9de01

File tree

2 files changed

+69
-125
lines changed

2 files changed

+69
-125
lines changed

src/config.rs

Lines changed: 55 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use indexmap::IndexMap;
55
use ini::{Ini, ParseOption};
66
use log::LevelFilter;
77
use windows::core::w;
8-
use windows::Win32::UI::Input::KeyboardAndMouse::{
9-
VIRTUAL_KEY, VK_LCONTROL, VK_LMENU, VK_LWIN, VK_RCONTROL, VK_RMENU, VK_RWIN,
10-
};
118

129
use crate::utils::{get_exe_folder, RegKey};
1310

@@ -189,8 +186,8 @@ impl Config {
189186
pub struct Hotkey {
190187
pub id: u32,
191188
pub name: String,
192-
pub modifier: [VIRTUAL_KEY; 2],
193-
pub code: u16,
189+
pub modifier: [u32; 2],
190+
pub code: u32,
194191
}
195192

196193
impl Hotkey {
@@ -205,123 +202,69 @@ impl Hotkey {
205202
})
206203
}
207204

208-
pub fn get_modifier(&self) -> u16 {
209-
self.modifier[0].0
205+
pub fn get_modifier(&self) -> u32 {
206+
self.modifier[0]
210207
}
211208

212-
pub fn parse(value: &str) -> Option<([VIRTUAL_KEY; 2], u16)> {
213-
let value = value.to_ascii_lowercase().replace(' ', "");
209+
pub fn parse(value: &str) -> Option<([u32; 2], u32)> {
210+
let value = value
211+
.to_ascii_lowercase()
212+
.replace(' ', "")
213+
.replace("vk_", "");
214214
let keys: Vec<&str> = value.split('+').collect();
215215
if keys.len() != 2 {
216216
return None;
217217
}
218218
let modifier = match keys[0] {
219-
"win" => [VK_LWIN, VK_RWIN],
220-
"alt" => [VK_LMENU, VK_RMENU],
221-
"ctrl" => [VK_LCONTROL, VK_RCONTROL],
219+
"win" => [0xe05b, 0xe05c],
220+
"alt" => [0x38, 0xe038],
221+
"ctrl" => [0x1d, 0xe01d],
222222
_ => {
223223
return None;
224224
}
225225
};
226+
// see <https://kbdlayout.info/kbdus/overview+scancodes>
226227
let code = match keys[1] {
227-
"backspace" => 0x08,
228-
"tab" => 0x09,
229-
"clear" => 0x0c,
230-
"enter" => 0x0d,
231-
"pause" => 0x13,
232-
"capslock" => 0x14,
233-
"escape" => 0x1b,
234-
"space" => 0x20,
235-
"pageup" => 0x21,
236-
"pagedown" => 0x22,
237-
"end" => 0x23,
238-
"home" => 0x24,
239-
"left" => 0x25,
240-
"up" => 0x26,
241-
"right" => 0x27,
242-
"down" => 0x28,
243-
"select" => 0x29,
244-
"print" => 0x2a,
245-
"printscreen" => 0x2c,
246-
"insert" => 0x2d,
247-
"delete" => 0x2e,
248-
249-
"0" => 0x30,
250-
"1" => 0x31,
251-
"2" => 0x32,
252-
"3" => 0x33,
253-
"4" => 0x34,
254-
"5" => 0x35,
255-
"6" => 0x36,
256-
"7" => 0x37,
257-
"8" => 0x38,
258-
"9" => 0x39,
259-
"a" => 0x41,
260-
"b" => 0x42,
261-
"c" => 0x43,
262-
"d" => 0x44,
263-
"e" => 0x45,
264-
"f" => 0x46,
265-
"g" => 0x47,
266-
"h" => 0x48,
267-
"i" => 0x49,
268-
"j" => 0x4a,
269-
"k" => 0x4b,
270-
"l" => 0x4c,
271-
"m" => 0x4d,
272-
"n" => 0x4e,
273-
"o" => 0x4f,
274-
"p" => 0x50,
275-
"q" => 0x51,
276-
"r" => 0x52,
277-
"s" => 0x53,
278-
"t" => 0x54,
279-
"u" => 0x55,
280-
"v" => 0x56,
281-
"w" => 0x57,
282-
"x" => 0x58,
283-
"y" => 0x59,
284-
"z" => 0x5a,
285-
286-
"f1" => 0x70,
287-
"f2" => 0x71,
288-
"f3" => 0x72,
289-
"f4" => 0x73,
290-
"f5" => 0x74,
291-
"f6" => 0x75,
292-
"f7" => 0x76,
293-
"f8" => 0x77,
294-
"f9" => 0x78,
295-
"f10" => 0x79,
296-
"f11" => 0x7a,
297-
"f12" => 0x7b,
298-
"numlock" => 0x90,
299-
"scrolllock" => 0x91,
300-
301-
":" | ";" | "vk_oem_1" => 0xba,
302-
"+" | "=" | "vk_oem_plus" => 0xbb,
303-
"<" | "," | "vk_oem_comma" => 0xbc,
304-
"-" | "_" | "vk_oem_minus" => 0xbd,
305-
">" | "." | "vk_oem_period" => 0xbe,
306-
"?" | "/" | "vk_oem_2" => 0xbf,
307-
"~" | "`" | "vk_oem_3" => 0xc0,
308-
"{" | "[" | "vk_oem_4" => 0xdb,
309-
"|" | "\\" | "vk_oem_5" => 0xdc,
310-
"}" | "]" | "vk_oem_6" => 0xdd,
311-
"\"" | "'" | "vk_oem_7" => 0xde,
312-
"§" | "!" | "vk_oem_8" => 0xdf,
313-
"vk_oem_102" => 0xe2,
314-
"vk_processkey" => 0xe5,
315-
"vk_packet" => 0xe7,
316-
"vk_attn" => 0xf6,
317-
"vk_crsel" => 0xf7,
318-
"vk_exsel" => 0xf8,
319-
"vk_ereof" => 0xf9,
320-
"vk_play" => 0xfa,
321-
"vk_zoom" => 0xfb,
322-
"vk_noname" => 0xfc,
323-
"vk_pa1" => 0xfd,
324-
"vk_oem_clear" => 0xfe,
228+
"-" | "_" | "oem_minus" => 0x0c,
229+
"+" | "=" | "oem_plus" => 0x0d,
230+
"bs" | "backspace" => 0x0e,
231+
"tab" => 0x0f,
232+
"{" | "[" | "oem_4" => 0x1a,
233+
"}" | "]" | "oem_6" => 0x1b,
234+
":" | ";" | "oem_1" => 0x27,
235+
"\"" | "'" | "oem_7" => 0x28,
236+
"~" | "`" | "oem_3" => 0x29,
237+
"|" | "\\" | "oem_5" => 0x2b,
238+
"<" | "," | "oem_comma" => 0x33,
239+
">" | "." | "oem_period" => 0x34,
240+
"?" | "/" | "oem_2" => 0x35,
241+
"capslock" => 0x3a,
242+
"f1" => 0x3b,
243+
"f2" => 0x3c,
244+
"f3" => 0x3d,
245+
"f4" => 0x3e,
246+
"f5" => 0x3f,
247+
"f6" => 0x40,
248+
"f7" => 0x41,
249+
"f8" => 0x42,
250+
"f9" => 0x43,
251+
"f10" => 0x44,
252+
"scrolllock" => 0x46,
253+
"prtsc" | "printscreen" => 0x54,
254+
"oem_102" => 0x56,
255+
"f11" => 0x57,
256+
"f12" => 0x58,
257+
"home" => 0xe047,
258+
"up" => 0xe048,
259+
"pageup" => 0xe049,
260+
"left" => 0xe04b,
261+
"right" => 0xe04d,
262+
"end" => 0xe04f,
263+
"down" => 0xe050,
264+
"pagedown" => 0xe051,
265+
"insert" => 0xe052,
266+
"delete" => 0xe053,
267+
"menu" => 0xe05d,
325268
_ => return None,
326269
};
327270
Some((modifier, code))
@@ -381,10 +324,7 @@ mod tests {
381324

382325
#[test]
383326
fn test_hotkey() {
384-
assert_eq!(Hotkey::parse("alt + `"), Some(([VK_LMENU, VK_RMENU], 0xc0)));
385-
assert_eq!(
386-
Hotkey::parse("alt + tab"),
387-
Some(([VK_LMENU, VK_RMENU], 0x09))
388-
);
327+
assert_eq!(Hotkey::parse("alt + `"), Some(([0x38, 0xe038], 0x29)));
328+
assert_eq!(Hotkey::parse("alt + tab"), Some(([0x38, 0xe038], 0x0f)));
389329
}
390330
}

src/keyboard.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use windows::Win32::{
1414
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
1515
System::LibraryLoader::GetModuleHandleW,
1616
UI::{
17-
Input::KeyboardAndMouse::{VIRTUAL_KEY, VK_ESCAPE, VK_LSHIFT, VK_RSHIFT},
17+
Input::KeyboardAndMouse::{SCANCODE_LSHIFT, SCANCODE_RSHIFT},
1818
WindowsAndMessaging::{
1919
CallNextHookEx, SendMessageW, SetWindowsHookExW, UnhookWindowsHookEx, HHOOK,
2020
KBDLLHOOKSTRUCT, WH_KEYBOARD_LL,
@@ -25,7 +25,7 @@ use windows::Win32::{
2525
static KEYBOARD_STATE: LazyLock<Mutex<Vec<HotKeyState>>> = LazyLock::new(|| Mutex::new(Vec::new()));
2626
static mut WINDOW: HWND = HWND(0 as _);
2727
static mut IS_SHIFT_PRESSED: bool = false;
28-
static mut PREVIOUS_KEYCODE: u16 = 0;
28+
static mut PREVIOUS_KEYCODE: u32 = 0;
2929

3030
#[derive(Debug)]
3131
pub struct KeyboardListener {
@@ -75,14 +75,18 @@ struct HotKeyState {
7575
unsafe extern "system" fn keyboard_proc(code: i32, w_param: WPARAM, l_param: LPARAM) -> LRESULT {
7676
let kbd_data: &KBDLLHOOKSTRUCT = &*(l_param.0 as *const _);
7777
debug!("keyboard {kbd_data:?}");
78-
let vk_code = VIRTUAL_KEY(kbd_data.vkCode as _);
7978
let mut is_modifier = false;
79+
let scan_code = if kbd_data.flags.0 & 1 == 0 {
80+
kbd_data.scanCode
81+
} else {
82+
kbd_data.scanCode | 0xe000
83+
};
8084
let is_key_pressed = || kbd_data.flags.0 & 128 == 0;
81-
if [VK_LSHIFT, VK_RSHIFT].contains(&vk_code) {
85+
if [SCANCODE_LSHIFT, SCANCODE_RSHIFT].contains(&scan_code) {
8286
IS_SHIFT_PRESSED = is_key_pressed();
8387
}
8488
for state in KEYBOARD_STATE.lock().iter_mut() {
85-
if state.hotkey.modifier.contains(&vk_code) {
89+
if state.hotkey.modifier.contains(&scan_code) {
8690
is_modifier = true;
8791
if is_key_pressed() {
8892
state.is_modifier_pressed = true;
@@ -107,26 +111,26 @@ unsafe extern "system" fn keyboard_proc(code: i32, w_param: WPARAM, l_param: LPA
107111
for state in KEYBOARD_STATE.lock().iter_mut() {
108112
if is_key_pressed() && state.is_modifier_pressed {
109113
let id = state.hotkey.id;
110-
if vk_code.0 == state.hotkey.code {
114+
if scan_code == state.hotkey.code {
111115
let reverse = if IS_SHIFT_PRESSED { 1 } else { 0 };
112116
if id == SWITCH_APPS_HOTKEY_ID {
113117
unsafe {
114118
SendMessageW(WINDOW, WM_USER_SWITCH_APPS, WPARAM(0), LPARAM(reverse))
115119
};
116-
PREVIOUS_KEYCODE = vk_code.0;
120+
PREVIOUS_KEYCODE = scan_code;
117121
return LRESULT(1);
118122
} else if id == SWITCH_WINDOWS_HOTKEY_ID && !IS_FOREGROUND_IN_BLACKLIST {
119123
unsafe {
120124
SendMessageW(WINDOW, WM_USER_SWITCH_WINDOWS, WPARAM(0), LPARAM(reverse))
121125
};
122-
PREVIOUS_KEYCODE = vk_code.0;
126+
PREVIOUS_KEYCODE = scan_code;
123127
return LRESULT(1);
124128
}
125-
} else if vk_code == VK_ESCAPE && id == SWITCH_APPS_HOTKEY_ID {
129+
} else if scan_code == 0x01 && id == SWITCH_APPS_HOTKEY_ID {
126130
unsafe {
127131
SendMessageW(WINDOW, WM_USER_SWITCH_APPS_CANCEL, WPARAM(0), LPARAM(0))
128132
};
129-
PREVIOUS_KEYCODE = vk_code.0;
133+
PREVIOUS_KEYCODE = scan_code;
130134
return LRESULT(1);
131135
}
132136
}

0 commit comments

Comments
 (0)