Add hotkey detection implementation to Windows
This commit is contained in:
parent
89805a0248
commit
474eae69d5
|
@ -16,6 +16,7 @@ lazycell = "1.3.0"
|
|||
anyhow = "1.0.38"
|
||||
thiserror = "1.0.23"
|
||||
regex = "1.4.3"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
widestring = "0.4.3"
|
||||
|
@ -24,9 +25,6 @@ widestring = "0.4.3"
|
|||
libc = "0.2.85"
|
||||
scopeguard = "1.1.0"
|
||||
|
||||
[target.'cfg(target_os="macos")'.dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
|
||||
|
|
|
@ -415,94 +415,89 @@ impl ShortcutKey {
|
|||
#[cfg(target_os = "windows")]
|
||||
pub fn to_code(&self) -> Option<u32> {
|
||||
let vkey = match self {
|
||||
Key::Alt => 0x12,
|
||||
Key::CapsLock => 0x14,
|
||||
Key::Control => 0x11,
|
||||
Key::Meta => 0x5B,
|
||||
Key::NumLock => 0x90,
|
||||
Key::Shift => 0xA0,
|
||||
Key::Enter => 0x0D,
|
||||
Key::Tab => 0x09,
|
||||
Key::Space => 0x20,
|
||||
Key::ArrowDown => 0x28,
|
||||
Key::ArrowLeft => 0x25,
|
||||
Key::ArrowRight => 0x27,
|
||||
Key::ArrowUp => 0x26,
|
||||
Key::End => 0x23,
|
||||
Key::Home => 0x24,
|
||||
Key::PageDown => 0x22,
|
||||
Key::PageUp => 0x21,
|
||||
Key::Escape => 0x1B,
|
||||
Key::Backspace => 0x08,
|
||||
Key::Insert => 0x2D,
|
||||
Key::Delete => 0x2E,
|
||||
Key::F1 => 0x70,
|
||||
Key::F2 => 0x71,
|
||||
Key::F3 => 0x72,
|
||||
Key::F4 => 0x73,
|
||||
Key::F5 => 0x74,
|
||||
Key::F6 => 0x75,
|
||||
Key::F7 => 0x76,
|
||||
Key::F8 => 0x77,
|
||||
Key::F9 => 0x78,
|
||||
Key::F10 => 0x79,
|
||||
Key::F11 => 0x7A,
|
||||
Key::F12 => 0x7B,
|
||||
Key::F13 => 0x7C,
|
||||
Key::F14 => 0x7D,
|
||||
Key::F15 => 0x7E,
|
||||
Key::F16 => 0x7F,
|
||||
Key::F17 => 0x80,
|
||||
Key::F18 => 0x81,
|
||||
Key::F19 => 0x82,
|
||||
Key::F20 => 0x83,
|
||||
Key::A => 0x41,
|
||||
Key::B => 0x42,
|
||||
Key::C => 0x43,
|
||||
Key::D => 0x44,
|
||||
Key::E => 0x45,
|
||||
Key::F => 0x46,
|
||||
Key::G => 0x47,
|
||||
Key::H => 0x48,
|
||||
Key::I => 0x49,
|
||||
Key::J => 0x4A,
|
||||
Key::K => 0x4B,
|
||||
Key::L => 0x4C,
|
||||
Key::M => 0x4D,
|
||||
Key::N => 0x4E,
|
||||
Key::O => 0x4F,
|
||||
Key::P => 0x50,
|
||||
Key::Q => 0x51,
|
||||
Key::R => 0x52,
|
||||
Key::S => 0x53,
|
||||
Key::T => 0x54,
|
||||
Key::U => 0x55,
|
||||
Key::V => 0x56,
|
||||
Key::W => 0x57,
|
||||
Key::X => 0x58,
|
||||
Key::Y => 0x59,
|
||||
Key::Z => 0x5A,
|
||||
Key::N0 => 0x30,
|
||||
Key::N1 => 0x31,
|
||||
Key::N2 => 0x32,
|
||||
Key::N3 => 0x33,
|
||||
Key::N4 => 0x34,
|
||||
Key::N5 => 0x35,
|
||||
Key::N6 => 0x36,
|
||||
Key::N7 => 0x37,
|
||||
Key::N8 => 0x38,
|
||||
Key::N9 => 0x39,
|
||||
Key::Numpad0 => 0x60,
|
||||
Key::Numpad1 => 0x61,
|
||||
Key::Numpad2 => 0x62,
|
||||
Key::Numpad3 => 0x63,
|
||||
Key::Numpad4 => 0x64,
|
||||
Key::Numpad5 => 0x65,
|
||||
Key::Numpad6 => 0x66,
|
||||
Key::Numpad7 => 0x67,
|
||||
Key::Numpad8 => 0x68,
|
||||
Key::Numpad9 => 0x69,
|
||||
Key::Raw(code) => *code,
|
||||
ShortcutKey::Alt => 0x12,
|
||||
ShortcutKey::Control => 0x11,
|
||||
ShortcutKey::Meta => 0x5B,
|
||||
ShortcutKey::Shift => 0xA0,
|
||||
ShortcutKey::Enter => 0x0D,
|
||||
ShortcutKey::Tab => 0x09,
|
||||
ShortcutKey::Space => 0x20,
|
||||
ShortcutKey::ArrowDown => 0x28,
|
||||
ShortcutKey::ArrowLeft => 0x25,
|
||||
ShortcutKey::ArrowRight => 0x27,
|
||||
ShortcutKey::ArrowUp => 0x26,
|
||||
ShortcutKey::End => 0x23,
|
||||
ShortcutKey::Home => 0x24,
|
||||
ShortcutKey::PageDown => 0x22,
|
||||
ShortcutKey::PageUp => 0x21,
|
||||
ShortcutKey::Insert => 0x2D,
|
||||
ShortcutKey::F1 => 0x70,
|
||||
ShortcutKey::F2 => 0x71,
|
||||
ShortcutKey::F3 => 0x72,
|
||||
ShortcutKey::F4 => 0x73,
|
||||
ShortcutKey::F5 => 0x74,
|
||||
ShortcutKey::F6 => 0x75,
|
||||
ShortcutKey::F7 => 0x76,
|
||||
ShortcutKey::F8 => 0x77,
|
||||
ShortcutKey::F9 => 0x78,
|
||||
ShortcutKey::F10 => 0x79,
|
||||
ShortcutKey::F11 => 0x7A,
|
||||
ShortcutKey::F12 => 0x7B,
|
||||
ShortcutKey::F13 => 0x7C,
|
||||
ShortcutKey::F14 => 0x7D,
|
||||
ShortcutKey::F15 => 0x7E,
|
||||
ShortcutKey::F16 => 0x7F,
|
||||
ShortcutKey::F17 => 0x80,
|
||||
ShortcutKey::F18 => 0x81,
|
||||
ShortcutKey::F19 => 0x82,
|
||||
ShortcutKey::F20 => 0x83,
|
||||
ShortcutKey::A => 0x41,
|
||||
ShortcutKey::B => 0x42,
|
||||
ShortcutKey::C => 0x43,
|
||||
ShortcutKey::D => 0x44,
|
||||
ShortcutKey::E => 0x45,
|
||||
ShortcutKey::F => 0x46,
|
||||
ShortcutKey::G => 0x47,
|
||||
ShortcutKey::H => 0x48,
|
||||
ShortcutKey::I => 0x49,
|
||||
ShortcutKey::J => 0x4A,
|
||||
ShortcutKey::K => 0x4B,
|
||||
ShortcutKey::L => 0x4C,
|
||||
ShortcutKey::M => 0x4D,
|
||||
ShortcutKey::N => 0x4E,
|
||||
ShortcutKey::O => 0x4F,
|
||||
ShortcutKey::P => 0x50,
|
||||
ShortcutKey::Q => 0x51,
|
||||
ShortcutKey::R => 0x52,
|
||||
ShortcutKey::S => 0x53,
|
||||
ShortcutKey::T => 0x54,
|
||||
ShortcutKey::U => 0x55,
|
||||
ShortcutKey::V => 0x56,
|
||||
ShortcutKey::W => 0x57,
|
||||
ShortcutKey::X => 0x58,
|
||||
ShortcutKey::Y => 0x59,
|
||||
ShortcutKey::Z => 0x5A,
|
||||
ShortcutKey::N0 => 0x30,
|
||||
ShortcutKey::N1 => 0x31,
|
||||
ShortcutKey::N2 => 0x32,
|
||||
ShortcutKey::N3 => 0x33,
|
||||
ShortcutKey::N4 => 0x34,
|
||||
ShortcutKey::N5 => 0x35,
|
||||
ShortcutKey::N6 => 0x36,
|
||||
ShortcutKey::N7 => 0x37,
|
||||
ShortcutKey::N8 => 0x38,
|
||||
ShortcutKey::N9 => 0x39,
|
||||
ShortcutKey::Numpad0 => 0x60,
|
||||
ShortcutKey::Numpad1 => 0x61,
|
||||
ShortcutKey::Numpad2 => 0x62,
|
||||
ShortcutKey::Numpad3 => 0x63,
|
||||
ShortcutKey::Numpad4 => 0x64,
|
||||
ShortcutKey::Numpad5 => 0x65,
|
||||
ShortcutKey::Numpad6 => 0x66,
|
||||
ShortcutKey::Numpad7 => 0x67,
|
||||
ShortcutKey::Numpad8 => 0x68,
|
||||
ShortcutKey::Numpad9 => 0x69,
|
||||
ShortcutKey::Raw(code) => *code,
|
||||
};
|
||||
Some(vkey)
|
||||
}
|
||||
|
|
|
@ -19,12 +19,18 @@
|
|||
|
||||
pub mod keys;
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use anyhow::Result;
|
||||
use keys::ShortcutKey;
|
||||
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)]
|
||||
pub struct HotKey {
|
||||
|
@ -33,7 +39,6 @@ pub struct HotKey {
|
|||
pub modifiers: Vec<ShortcutKey>,
|
||||
}
|
||||
|
||||
// TODO: test all methods
|
||||
impl HotKey {
|
||||
pub fn new(id: i32, shortcut: &str) -> Result<Self> {
|
||||
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)]
|
||||
pub enum HotKeyError {
|
||||
#[error("invalid hotkey shortcut, `{0}` is not a valid key")]
|
||||
|
@ -100,16 +118,22 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse_correctly() {
|
||||
assert_eq!(HotKey::new(1, "CTRL+V").unwrap(), HotKey {
|
||||
id: 1,
|
||||
key: ShortcutKey::V,
|
||||
modifiers: vec![ShortcutKey::Control],
|
||||
});
|
||||
assert_eq!(HotKey::new(2, "SHIFT + Ctrl + v").unwrap(), HotKey {
|
||||
id: 2,
|
||||
key: ShortcutKey::V,
|
||||
modifiers: vec![ShortcutKey::Shift, ShortcutKey::Control],
|
||||
});
|
||||
assert_eq!(
|
||||
HotKey::new(1, "CTRL+V").unwrap(),
|
||||
HotKey {
|
||||
id: 1,
|
||||
key: ShortcutKey::V,
|
||||
modifiers: vec![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());
|
||||
}
|
||||
|
||||
|
@ -124,4 +148,4 @@ mod tests {
|
|||
assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_alt());
|
||||
assert!(!HotKey::new(1, "SHIFT+ V").unwrap().has_meta());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ pub mod evdev;
|
|||
#[cfg(target_os = "macos")]
|
||||
pub mod mac;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
|
@ -85,9 +84,9 @@ impl Default for SourceCreationOptions {
|
|||
}
|
||||
|
||||
#[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");
|
||||
Ok(Box::new(win32::Win32Source::new()))
|
||||
Ok(Box::new(win32::Win32Source::new(&options.hotkeys)))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
* 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 log::{error, trace, warn};
|
||||
use log::{debug, error, trace, warn};
|
||||
use widestring::U16CStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::event::Variant::*;
|
||||
use crate::{event::{HotKeyEvent, Variant::*}, hotkey::{HotKey}};
|
||||
use crate::event::{InputEvent, Key, KeyboardEvent, Variant};
|
||||
use crate::event::{Key::*, MouseButton, MouseEvent};
|
||||
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_MOUSE: i32 = 2;
|
||||
const INPUT_EVENT_TYPE_HOTKEY: i32 = 3;
|
||||
|
||||
const INPUT_STATUS_PRESSED: i32 = 1;
|
||||
const INPUT_STATUS_RELEASED: i32 = 2;
|
||||
|
@ -62,10 +63,18 @@ pub struct RawInputEvent {
|
|||
pub status: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RawHotKey {
|
||||
pub id: i32,
|
||||
pub code: u32,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
#[link(name = "espansodetect", kind = "static")]
|
||||
extern "C" {
|
||||
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(
|
||||
window: *const c_void,
|
||||
|
@ -78,20 +87,24 @@ extern "C" {
|
|||
pub struct Win32Source {
|
||||
handle: *mut c_void,
|
||||
callback: LazyCell<SourceCallback>,
|
||||
hotkeys: Vec<HotKey>,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl Win32Source {
|
||||
pub fn new() -> Win32Source {
|
||||
pub fn new(hotkeys: &[HotKey]) -> Win32Source {
|
||||
Self {
|
||||
handle: std::ptr::null_mut(),
|
||||
callback: LazyCell::new(),
|
||||
hotkeys: hotkeys.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Source for Win32Source {
|
||||
fn initialize(&mut self) -> Result<()> {
|
||||
|
||||
|
||||
let mut error_code = 0;
|
||||
let handle = unsafe { detect_initialize(self as *const Win32Source, &mut error_code) };
|
||||
|
||||
|
@ -104,6 +117,23 @@ impl Source for Win32Source {
|
|||
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;
|
||||
|
||||
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)]
|
||||
pub enum Win32SourceError {
|
||||
#[error("window registration failed")]
|
||||
|
@ -225,6 +284,12 @@ impl From<RawInputEvent> for Option<InputEvent> {
|
|||
return Some(InputEvent::Mouse(MouseEvent { button, status }));
|
||||
}
|
||||
}
|
||||
// Hotkey events
|
||||
INPUT_EVENT_TYPE_HOTKEY => {
|
||||
return Some(InputEvent::HotKey(HotKeyEvent {
|
||||
hotkey_id: raw.key_code
|
||||
}))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,17 @@ LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM w
|
|||
SetWindowLongPtrW(window, GWLP_USERDATA, NULL);
|
||||
|
||||
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
|
||||
{
|
||||
InputEvent event = {};
|
||||
|
@ -322,6 +333,10 @@ void * detect_initialize(void *_self, int32_t *error_code)
|
|||
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)
|
||||
{
|
||||
if (window)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#define INPUT_EVENT_TYPE_KEYBOARD 1
|
||||
#define INPUT_EVENT_TYPE_MOUSE 2
|
||||
#define INPUT_EVENT_TYPE_HOTKEY 3
|
||||
|
||||
#define INPUT_STATUS_PRESSED 1
|
||||
#define INPUT_STATUS_RELEASED 2
|
||||
|
@ -50,7 +51,8 @@ typedef struct {
|
|||
int32_t buffer_len;
|
||||
|
||||
// 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;
|
||||
|
||||
// Left or Right variant
|
||||
|
@ -60,12 +62,21 @@ typedef struct {
|
|||
int32_t status;
|
||||
} InputEvent;
|
||||
|
||||
typedef struct {
|
||||
int32_t hk_id;
|
||||
uint32_t key_code;
|
||||
uint32_t flags;
|
||||
} HotKey;
|
||||
|
||||
typedef void (*EventCallback)(void * rust_istance, InputEvent data);
|
||||
|
||||
|
||||
// Initialize the Raw Input API and the Window.
|
||||
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.
|
||||
extern "C" int32_t detect_eventloop(void * window, EventCallback callback);
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ fn main() {
|
|||
let mut source = get_source(SourceCreationOptions {
|
||||
hotkeys: vec![
|
||||
HotKey::new(1, "OPTION+SPACE").unwrap(),
|
||||
HotKey::new(2, "CMD+OPTION+3").unwrap(),
|
||||
HotKey::new(2, "CTRL+OPTION+3").unwrap(),
|
||||
],
|
||||
..Default::default()
|
||||
}).unwrap();
|
||||
|
|
Loading…
Reference in New Issue
Block a user