WIP: clap
This commit is contained in:
142
crates/baseview/src/win/hook.rs
Normal file
142
crates/baseview/src/win/hook.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
ffi::c_int,
|
||||
ptr,
|
||||
sync::{LazyLock, RwLock},
|
||||
};
|
||||
|
||||
use winapi::{
|
||||
shared::{
|
||||
minwindef::{LPARAM, WPARAM},
|
||||
windef::{HHOOK, HWND, POINT},
|
||||
},
|
||||
um::{
|
||||
libloaderapi::GetModuleHandleW,
|
||||
processthreadsapi::GetCurrentThreadId,
|
||||
winuser::{
|
||||
CallNextHookEx, SetWindowsHookExW, UnhookWindowsHookEx, HC_ACTION, MSG, PM_REMOVE,
|
||||
WH_GETMESSAGE, WM_CHAR, WM_KEYDOWN, WM_KEYUP, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
|
||||
WM_USER,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::win::wnd_proc;
|
||||
|
||||
// track all windows opened by this instance of baseview
|
||||
// we use an RwLock here since the vast majority of uses (event interceptions)
|
||||
// will only need to read from the HashSet
|
||||
static HOOK_STATE: LazyLock<RwLock<KeyboardHookState>> = LazyLock::new(|| RwLock::default());
|
||||
|
||||
pub(crate) struct KeyboardHookHandle(HWNDWrapper);
|
||||
|
||||
#[derive(Default)]
|
||||
struct KeyboardHookState {
|
||||
hook: Option<HHOOK>,
|
||||
open_windows: HashSet<HWNDWrapper>,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
|
||||
struct HWNDWrapper(HWND);
|
||||
|
||||
// SAFETY: it's a pointer behind an RwLock. we'll live
|
||||
unsafe impl Send for KeyboardHookState {}
|
||||
unsafe impl Sync for KeyboardHookState {}
|
||||
|
||||
// SAFETY: we never access the underlying HWND ourselves, just use it as a HashSet entry
|
||||
unsafe impl Send for HWNDWrapper {}
|
||||
unsafe impl Sync for HWNDWrapper {}
|
||||
|
||||
impl Drop for KeyboardHookHandle {
|
||||
fn drop(&mut self) {
|
||||
deinit_keyboard_hook(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize keyboard hook
|
||||
// some DAWs (particularly Ableton) intercept incoming keyboard messages,
|
||||
// but we're naughty so we intercept them right back
|
||||
pub(crate) fn init_keyboard_hook(hwnd: HWND) -> KeyboardHookHandle {
|
||||
let state = &mut *HOOK_STATE.write().unwrap();
|
||||
|
||||
// register hwnd to global window set
|
||||
state.open_windows.insert(HWNDWrapper(hwnd));
|
||||
|
||||
if state.hook.is_some() {
|
||||
// keyboard hook already exists, just return handle
|
||||
KeyboardHookHandle(HWNDWrapper(hwnd))
|
||||
} else {
|
||||
// keyboard hook doesn't exist (no windows open before this), create it
|
||||
let new_hook = unsafe {
|
||||
SetWindowsHookExW(
|
||||
WH_GETMESSAGE,
|
||||
Some(keyboard_hook_callback),
|
||||
GetModuleHandleW(ptr::null()),
|
||||
GetCurrentThreadId(),
|
||||
)
|
||||
};
|
||||
|
||||
state.hook = Some(new_hook);
|
||||
|
||||
KeyboardHookHandle(HWNDWrapper(hwnd))
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit_keyboard_hook(hwnd: HWNDWrapper) {
|
||||
let state = &mut *HOOK_STATE.write().unwrap();
|
||||
|
||||
state.open_windows.remove(&hwnd);
|
||||
|
||||
if state.open_windows.is_empty() {
|
||||
if let Some(hhook) = state.hook {
|
||||
unsafe {
|
||||
UnhookWindowsHookEx(hhook);
|
||||
}
|
||||
|
||||
state.hook = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn keyboard_hook_callback(
|
||||
n_code: c_int, wparam: WPARAM, lparam: LPARAM,
|
||||
) -> isize {
|
||||
let msg = lparam as *mut MSG;
|
||||
|
||||
if n_code == HC_ACTION && wparam == PM_REMOVE as usize && offer_message_to_baseview(msg) {
|
||||
*msg = MSG {
|
||||
hwnd: ptr::null_mut(),
|
||||
message: WM_USER,
|
||||
wParam: 0,
|
||||
lParam: 0,
|
||||
time: 0,
|
||||
pt: POINT { x: 0, y: 0 },
|
||||
};
|
||||
|
||||
0
|
||||
} else {
|
||||
CallNextHookEx(ptr::null_mut(), n_code, wparam, lparam)
|
||||
}
|
||||
}
|
||||
|
||||
// check if `msg` is a keyboard message addressed to a window
|
||||
// in KeyboardHookState::open_windows, and intercept it if so
|
||||
unsafe fn offer_message_to_baseview(msg: *mut MSG) -> bool {
|
||||
let msg = &*msg;
|
||||
|
||||
// if this isn't a keyboard message, ignore it
|
||||
match msg.message {
|
||||
WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP | WM_CHAR | WM_SYSCHAR => {}
|
||||
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
// check if this is one of our windows. if so, intercept it
|
||||
if HOOK_STATE.read().unwrap().open_windows.contains(&HWNDWrapper(msg.hwnd)) {
|
||||
let _ = wnd_proc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
Reference in New Issue
Block a user