Add hotkey detection implementation to Windows

This commit is contained in:
Federico Terzi 2021-03-14 18:24:37 +01:00
parent 89805a0248
commit 474eae69d5
8 changed files with 220 additions and 113 deletions

View File

@ -16,6 +16,7 @@ lazycell = "1.3.0"
anyhow = "1.0.38" anyhow = "1.0.38"
thiserror = "1.0.23" thiserror = "1.0.23"
regex = "1.4.3" regex = "1.4.3"
lazy_static = "1.4.0"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
widestring = "0.4.3" widestring = "0.4.3"
@ -24,9 +25,6 @@ widestring = "0.4.3"
libc = "0.2.85" libc = "0.2.85"
scopeguard = "1.1.0" scopeguard = "1.1.0"
[target.'cfg(target_os="macos")'.dependencies]
lazy_static = "1.4.0"
[build-dependencies] [build-dependencies]
cc = "1.0.66" cc = "1.0.66"

View File

@ -415,94 +415,89 @@ impl ShortcutKey {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn to_code(&self) -> Option<u32> { pub fn to_code(&self) -> Option<u32> {
let vkey = match self { let vkey = match self {
Key::Alt => 0x12, ShortcutKey::Alt => 0x12,
Key::CapsLock => 0x14, ShortcutKey::Control => 0x11,
Key::Control => 0x11, ShortcutKey::Meta => 0x5B,
Key::Meta => 0x5B, ShortcutKey::Shift => 0xA0,
Key::NumLock => 0x90, ShortcutKey::Enter => 0x0D,
Key::Shift => 0xA0, ShortcutKey::Tab => 0x09,
Key::Enter => 0x0D, ShortcutKey::Space => 0x20,
Key::Tab => 0x09, ShortcutKey::ArrowDown => 0x28,
Key::Space => 0x20, ShortcutKey::ArrowLeft => 0x25,
Key::ArrowDown => 0x28, ShortcutKey::ArrowRight => 0x27,
Key::ArrowLeft => 0x25, ShortcutKey::ArrowUp => 0x26,
Key::ArrowRight => 0x27, ShortcutKey::End => 0x23,
Key::ArrowUp => 0x26, ShortcutKey::Home => 0x24,
Key::End => 0x23, ShortcutKey::PageDown => 0x22,
Key::Home => 0x24, ShortcutKey::PageUp => 0x21,
Key::PageDown => 0x22, ShortcutKey::Insert => 0x2D,
Key::PageUp => 0x21, ShortcutKey::F1 => 0x70,
Key::Escape => 0x1B, ShortcutKey::F2 => 0x71,
Key::Backspace => 0x08, ShortcutKey::F3 => 0x72,
Key::Insert => 0x2D, ShortcutKey::F4 => 0x73,
Key::Delete => 0x2E, ShortcutKey::F5 => 0x74,
Key::F1 => 0x70, ShortcutKey::F6 => 0x75,
Key::F2 => 0x71, ShortcutKey::F7 => 0x76,
Key::F3 => 0x72, ShortcutKey::F8 => 0x77,
Key::F4 => 0x73, ShortcutKey::F9 => 0x78,
Key::F5 => 0x74, ShortcutKey::F10 => 0x79,
Key::F6 => 0x75, ShortcutKey::F11 => 0x7A,
Key::F7 => 0x76, ShortcutKey::F12 => 0x7B,
Key::F8 => 0x77, ShortcutKey::F13 => 0x7C,
Key::F9 => 0x78, ShortcutKey::F14 => 0x7D,
Key::F10 => 0x79, ShortcutKey::F15 => 0x7E,
Key::F11 => 0x7A, ShortcutKey::F16 => 0x7F,
Key::F12 => 0x7B, ShortcutKey::F17 => 0x80,
Key::F13 => 0x7C, ShortcutKey::F18 => 0x81,
Key::F14 => 0x7D, ShortcutKey::F19 => 0x82,
Key::F15 => 0x7E, ShortcutKey::F20 => 0x83,
Key::F16 => 0x7F, ShortcutKey::A => 0x41,
Key::F17 => 0x80, ShortcutKey::B => 0x42,
Key::F18 => 0x81, ShortcutKey::C => 0x43,
Key::F19 => 0x82, ShortcutKey::D => 0x44,
Key::F20 => 0x83, ShortcutKey::E => 0x45,
Key::A => 0x41, ShortcutKey::F => 0x46,
Key::B => 0x42, ShortcutKey::G => 0x47,
Key::C => 0x43, ShortcutKey::H => 0x48,
Key::D => 0x44, ShortcutKey::I => 0x49,
Key::E => 0x45, ShortcutKey::J => 0x4A,
Key::F => 0x46, ShortcutKey::K => 0x4B,
Key::G => 0x47, ShortcutKey::L => 0x4C,
Key::H => 0x48, ShortcutKey::M => 0x4D,
Key::I => 0x49, ShortcutKey::N => 0x4E,
Key::J => 0x4A, ShortcutKey::O => 0x4F,
Key::K => 0x4B, ShortcutKey::P => 0x50,
Key::L => 0x4C, ShortcutKey::Q => 0x51,
Key::M => 0x4D, ShortcutKey::R => 0x52,
Key::N => 0x4E, ShortcutKey::S => 0x53,
Key::O => 0x4F, ShortcutKey::T => 0x54,
Key::P => 0x50, ShortcutKey::U => 0x55,
Key::Q => 0x51, ShortcutKey::V => 0x56,
Key::R => 0x52, ShortcutKey::W => 0x57,
Key::S => 0x53, ShortcutKey::X => 0x58,
Key::T => 0x54, ShortcutKey::Y => 0x59,
Key::U => 0x55, ShortcutKey::Z => 0x5A,
Key::V => 0x56, ShortcutKey::N0 => 0x30,
Key::W => 0x57, ShortcutKey::N1 => 0x31,
Key::X => 0x58, ShortcutKey::N2 => 0x32,
Key::Y => 0x59, ShortcutKey::N3 => 0x33,
Key::Z => 0x5A, ShortcutKey::N4 => 0x34,
Key::N0 => 0x30, ShortcutKey::N5 => 0x35,
Key::N1 => 0x31, ShortcutKey::N6 => 0x36,
Key::N2 => 0x32, ShortcutKey::N7 => 0x37,
Key::N3 => 0x33, ShortcutKey::N8 => 0x38,
Key::N4 => 0x34, ShortcutKey::N9 => 0x39,
Key::N5 => 0x35, ShortcutKey::Numpad0 => 0x60,
Key::N6 => 0x36, ShortcutKey::Numpad1 => 0x61,
Key::N7 => 0x37, ShortcutKey::Numpad2 => 0x62,
Key::N8 => 0x38, ShortcutKey::Numpad3 => 0x63,
Key::N9 => 0x39, ShortcutKey::Numpad4 => 0x64,
Key::Numpad0 => 0x60, ShortcutKey::Numpad5 => 0x65,
Key::Numpad1 => 0x61, ShortcutKey::Numpad6 => 0x66,
Key::Numpad2 => 0x62, ShortcutKey::Numpad7 => 0x67,
Key::Numpad3 => 0x63, ShortcutKey::Numpad8 => 0x68,
Key::Numpad4 => 0x64, ShortcutKey::Numpad9 => 0x69,
Key::Numpad5 => 0x65, ShortcutKey::Raw(code) => *code,
Key::Numpad6 => 0x66,
Key::Numpad7 => 0x67,
Key::Numpad8 => 0x68,
Key::Numpad9 => 0x69,
Key::Raw(code) => *code,
}; };
Some(vkey) Some(vkey)
} }

View File

@ -19,12 +19,18 @@
pub mod keys; pub mod keys;
use std::fmt::Display;
use anyhow::Result; use anyhow::Result;
use keys::ShortcutKey; use keys::ShortcutKey;
use thiserror::Error; use thiserror::Error;
static MODIFIERS: &[ShortcutKey; 4] = &[ShortcutKey::Control, ShortcutKey::Alt, ShortcutKey::Shift, ShortcutKey::Meta]; static MODIFIERS: &[ShortcutKey; 4] = &[
ShortcutKey::Control,
ShortcutKey::Alt,
ShortcutKey::Shift,
ShortcutKey::Meta,
];
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct HotKey { pub struct HotKey {
@ -33,7 +39,6 @@ pub struct HotKey {
pub modifiers: Vec<ShortcutKey>, pub modifiers: Vec<ShortcutKey>,
} }
// TODO: test all methods
impl HotKey { impl HotKey {
pub fn new(id: i32, shortcut: &str) -> Result<Self> { pub fn new(id: i32, shortcut: &str) -> Result<Self> {
let tokens: Vec<String> = shortcut let tokens: Vec<String> = shortcut
@ -85,6 +90,19 @@ impl HotKey {
} }
} }
impl Display for HotKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str_modifiers: Vec<String> = self.modifiers.iter().map(|m| m.to_string()).collect();
let modifiers = str_modifiers.join("+");
write!(
f,
"{}+{}",
&modifiers,
&self.key
)
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum HotKeyError { pub enum HotKeyError {
#[error("invalid hotkey shortcut, `{0}` is not a valid key")] #[error("invalid hotkey shortcut, `{0}` is not a valid key")]
@ -100,16 +118,22 @@ mod tests {
#[test] #[test]
fn parse_correctly() { fn parse_correctly() {
assert_eq!(HotKey::new(1, "CTRL+V").unwrap(), HotKey { assert_eq!(
id: 1, HotKey::new(1, "CTRL+V").unwrap(),
key: ShortcutKey::V, HotKey {
modifiers: vec![ShortcutKey::Control], id: 1,
}); key: ShortcutKey::V,
assert_eq!(HotKey::new(2, "SHIFT + Ctrl + v").unwrap(), HotKey { modifiers: vec![ShortcutKey::Control],
id: 2, }
key: ShortcutKey::V, );
modifiers: vec![ShortcutKey::Shift, ShortcutKey::Control], assert_eq!(
}); HotKey::new(2, "SHIFT + Ctrl + v").unwrap(),
HotKey {
id: 2,
key: ShortcutKey::V,
modifiers: vec![ShortcutKey::Shift, ShortcutKey::Control],
}
);
assert!(HotKey::new(3, "invalid").is_err()); assert!(HotKey::new(3, "invalid").is_err());
} }
@ -124,4 +148,4 @@ mod tests {
assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_alt()); assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_alt());
assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_meta()); assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_meta());
} }
} }

View File

@ -37,7 +37,6 @@ pub mod evdev;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub mod mac; pub mod mac;
#[cfg(target_os = "macos")]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;
@ -85,9 +84,9 @@ impl Default for SourceCreationOptions {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn get_source(_options: SourceCreationOptions) -> Result<Box<dyn Source>> { pub fn get_source(options: SourceCreationOptions) -> Result<Box<dyn Source>> {
info!("using Win32Source"); info!("using Win32Source");
Ok(Box::new(win32::Win32Source::new())) Ok(Box::new(win32::Win32Source::new(&options.hotkeys)))
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View File

@ -17,16 +17,16 @@
* along with espanso. If not, see <https://www.gnu.org/licenses/>. * along with espanso. If not, see <https://www.gnu.org/licenses/>.
*/ */
use std::ffi::c_void; use std::{convert::TryInto, ffi::c_void};
use lazycell::LazyCell; use lazycell::LazyCell;
use log::{error, trace, warn}; use log::{debug, error, trace, warn};
use widestring::U16CStr; use widestring::U16CStr;
use anyhow::Result; use anyhow::Result;
use thiserror::Error; use thiserror::Error;
use crate::event::Variant::*; use crate::{event::{HotKeyEvent, Variant::*}, hotkey::{HotKey}};
use crate::event::{InputEvent, Key, KeyboardEvent, Variant}; use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
use crate::event::{Key::*, MouseButton, MouseEvent}; use crate::event::{Key::*, MouseButton, MouseEvent};
use crate::{event::Status::*, Source, SourceCallback}; use crate::{event::Status::*, Source, SourceCallback};
@ -36,6 +36,7 @@ const INPUT_RIGHT_VARIANT: i32 = 2;
const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1; const INPUT_EVENT_TYPE_KEYBOARD: i32 = 1;
const INPUT_EVENT_TYPE_MOUSE: i32 = 2; const INPUT_EVENT_TYPE_MOUSE: i32 = 2;
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
const INPUT_STATUS_PRESSED: i32 = 1; const INPUT_STATUS_PRESSED: i32 = 1;
const INPUT_STATUS_RELEASED: i32 = 2; const INPUT_STATUS_RELEASED: i32 = 2;
@ -62,10 +63,18 @@ pub struct RawInputEvent {
pub status: i32, pub status: i32,
} }
#[repr(C)]
pub struct RawHotKey {
pub id: i32,
pub code: u32,
pub flags: u32,
}
#[allow(improper_ctypes)] #[allow(improper_ctypes)]
#[link(name = "espansodetect", kind = "static")] #[link(name = "espansodetect", kind = "static")]
extern "C" { extern "C" {
pub fn detect_initialize(_self: *const Win32Source, error_code: *mut i32) -> *mut c_void; pub fn detect_initialize(_self: *const Win32Source, error_code: *mut i32) -> *mut c_void;
pub fn detect_register_hotkey(window: *const c_void, hotkey: RawHotKey) -> i32;
pub fn detect_eventloop( pub fn detect_eventloop(
window: *const c_void, window: *const c_void,
@ -78,20 +87,24 @@ extern "C" {
pub struct Win32Source { pub struct Win32Source {
handle: *mut c_void, handle: *mut c_void,
callback: LazyCell<SourceCallback>, callback: LazyCell<SourceCallback>,
hotkeys: Vec<HotKey>,
} }
#[allow(clippy::new_without_default)] #[allow(clippy::new_without_default)]
impl Win32Source { impl Win32Source {
pub fn new() -> Win32Source { pub fn new(hotkeys: &[HotKey]) -> Win32Source {
Self { Self {
handle: std::ptr::null_mut(), handle: std::ptr::null_mut(),
callback: LazyCell::new(), callback: LazyCell::new(),
hotkeys: hotkeys.to_vec(),
} }
} }
} }
impl Source for Win32Source { impl Source for Win32Source {
fn initialize(&mut self) -> Result<()> { fn initialize(&mut self) -> Result<()> {
let mut error_code = 0; let mut error_code = 0;
let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) }; let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) };
@ -104,6 +117,23 @@ impl Source for Win32Source {
return Err(error.into()); return Err(error.into());
} }
// Register the hotkeys
self
.hotkeys
.iter()
.for_each(|hk| {
let raw = convert_hotkey_to_raw(&hk);
if let Some(raw_hk) = raw {
if unsafe { detect_register_hotkey(handle, raw_hk) } == 0 {
error!("unable to register hotkey: {}", hk);
} else {
debug!("registered hotkey: {}", hk);
}
} else {
error!("unable to generate raw hotkey mapping: {}", hk);
}
});
self.handle = handle; self.handle = handle;
Ok(()) Ok(())
@ -156,6 +186,35 @@ impl Drop for Win32Source {
} }
} }
fn convert_hotkey_to_raw(hk: &HotKey) -> Option<RawHotKey> {
let key_code = hk.key.to_code()?;
let code: Result<u32, _> = key_code.try_into();
if let Ok(code) = code {
let mut flags = 0x4000; // NOREPEAT flags
if hk.has_ctrl() {
flags |= 0x0002;
}
if hk.has_alt() {
flags |= 0x0001;
}
if hk.has_meta() {
flags |= 0x0008;
}
if hk.has_shift() {
flags |= 0x0004;
}
Some(RawHotKey {
id: hk.id,
code,
flags,
})
} else {
error!("unable to generate raw hotkey, the key_code is overflowing");
None
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum Win32SourceError { pub enum Win32SourceError {
#[error("window registration failed")] #[error("window registration failed")]
@ -225,6 +284,12 @@ impl From<RawInputEvent> for Option<InputEvent> {
return Some(InputEvent::Mouse(MouseEvent { button, status })); return Some(InputEvent::Mouse(MouseEvent { button, status }));
} }
} }
// Hotkey events
INPUT_EVENT_TYPE_HOTKEY => {
return Some(InputEvent::HotKey(HotKeyEvent {
hotkey_id: raw.key_code
}))
}
_ => {} _ => {}
} }

View File

@ -75,6 +75,17 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w
SetWindowLongPtrW(window, GWLP_USERDATA, NULL); SetWindowLongPtrW(window, GWLP_USERDATA, NULL);
return 0L; return 0L;
case WM_HOTKEY: // Hotkeys
{
InputEvent event = {};
event.event_type = INPUT_EVENT_TYPE_HOTKEY;
event.key_code = (int32_t) wp;
if (variables->rust_instance != NULL && variables->event_callback != NULL)
{
variables->event_callback(variables->rust_instance, event);
}
break;
}
case WM_INPUT: // Message relative to the RAW INPUT events case WM_INPUT: // Message relative to the RAW INPUT events
{ {
InputEvent event = {}; InputEvent event = {};
@ -322,6 +333,10 @@ void * detect_initialize(void *_self, int32_t *error_code)
return window; return window;
} }
int32_t detect_register_hotkey(void * window, HotKey hotkey) {
return RegisterHotKey((HWND)window, hotkey.hk_id, hotkey.flags, hotkey.key_code);
}
int32_t detect_eventloop(void * window, EventCallback _callback) int32_t detect_eventloop(void * window, EventCallback _callback)
{ {
if (window) if (window)

View File

@ -24,6 +24,7 @@
#define INPUT_EVENT_TYPE_KEYBOARD 1 #define INPUT_EVENT_TYPE_KEYBOARD 1
#define INPUT_EVENT_TYPE_MOUSE 2 #define INPUT_EVENT_TYPE_MOUSE 2
#define INPUT_EVENT_TYPE_HOTKEY 3
#define INPUT_STATUS_PRESSED 1 #define INPUT_STATUS_PRESSED 1
#define INPUT_STATUS_RELEASED 2 #define INPUT_STATUS_RELEASED 2
@ -50,7 +51,8 @@ typedef struct {
int32_t buffer_len; int32_t buffer_len;
// Virtual key code of the pressed key in case of keyboard events // Virtual key code of the pressed key in case of keyboard events
// Mouse button code otherwise. // Mouse button code for mouse events.
// Hotkey id for hotkey events
int32_t key_code; int32_t key_code;
// Left or Right variant // Left or Right variant
@ -60,12 +62,21 @@ typedef struct {
int32_t status; int32_t status;
} InputEvent; } InputEvent;
typedef struct {
int32_t hk_id;
uint32_t key_code;
uint32_t flags;
} HotKey;
typedef void (*EventCallback)(void * rust_istance, InputEvent data); typedef void (*EventCallback)(void * rust_istance, InputEvent data);
// Initialize the Raw Input API and the Window. // Initialize the Raw Input API and the Window.
extern "C" void * detect_initialize(void * rust_istance, int32_t *error_code); extern "C" void * detect_initialize(void * rust_istance, int32_t *error_code);
// Register the given hotkey, return a non-zero code if successful
extern "C" int32_t detect_register_hotkey(void * window, HotKey hotkey);
// Run the event loop. Blocking call. // Run the event loop. Blocking call.
extern "C" int32_t detect_eventloop(void * window, EventCallback callback); extern "C" int32_t detect_eventloop(void * window, EventCallback callback);

View File

@ -63,7 +63,7 @@ fn main() {
let mut source = get_source(SourceCreationOptions { let mut source = get_source(SourceCreationOptions {
hotkeys: vec![ hotkeys: vec![
HotKey::new(1, "OPTION+SPACE").unwrap(), HotKey::new(1, "OPTION+SPACE").unwrap(),
HotKey::new(2, "CMD+OPTION+3").unwrap(), HotKey::new(2, "CTRL+OPTION+3").unwrap(),
], ],
..Default::default() ..Default::default()
}).unwrap(); }).unwrap();