Skip to content

Commit 36989ff

Browse files
Replace dtor with HashSet-based approach
1 parent 0025f71 commit 36989ff

3 files changed

Lines changed: 77 additions & 58 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ nix = "0.22.0"
3030
[target.'cfg(target_os="windows")'.dependencies]
3131
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "shellapi", "winerror"] }
3232
uuid = { version = "0.8", features = ["v4"], optional = true }
33-
dtor = "=0.0.6"
3433

3534
[target.'cfg(target_os="macos")'.dependencies]
3635
cocoa = "0.24.0"

src/win/hook.rs

Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,99 @@
11
use std::{
2+
collections::HashSet,
23
ffi::c_int,
34
ptr,
4-
sync::{Mutex, Once},
5+
sync::{LazyLock, Mutex, RwLock},
56
};
67

7-
use dtor::dtor;
88
use winapi::{
99
shared::{
1010
minwindef::{LPARAM, WPARAM},
11-
windef::{HHOOK, POINT},
11+
windef::{HHOOK, HWND, POINT},
1212
},
1313
um::{
1414
libloaderapi::GetModuleHandleA,
1515
processthreadsapi::GetCurrentThreadId,
1616
winuser::{
17-
CallNextHookEx, GetClassNameA, GetWindowLongPtrW, SetWindowsHookExA,
18-
UnhookWindowsHookEx, GWLP_USERDATA, HC_ACTION, MSG, PM_REMOVE, WH_GETMESSAGE, WM_CHAR,
19-
WM_KEYDOWN, WM_KEYUP, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER,
17+
CallNextHookEx, SetWindowsHookExA, UnhookWindowsHookEx, HC_ACTION, MSG, PM_REMOVE,
18+
WH_GETMESSAGE, WM_CHAR, WM_KEYDOWN, WM_KEYUP, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
19+
WM_USER,
2020
},
2121
},
2222
};
2323

24-
use crate::win::{wnd_proc, WindowState};
24+
use crate::win::wnd_proc;
2525

26-
static HOOK: Mutex<WinKeyboardHook> = Mutex::new(WinKeyboardHook::new());
27-
static ONCE: Once = Once::new();
26+
static HOOK: Mutex<Option<KeyboardHook>> = Mutex::new(None);
27+
28+
// track all windows opened by this instance of baseview
29+
// we use an RwLock here since the vast majority of uses (event interceptions)
30+
// will only need to read from the HashSet
31+
static OPEN_WINDOWS: LazyLock<RwLock<HashSet<HWNDWrapper>>> = LazyLock::new(|| RwLock::default());
32+
33+
pub(crate) struct KeyboardHookHandle(HWNDWrapper);
34+
35+
struct KeyboardHook(HHOOK);
36+
37+
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
38+
struct HWNDWrapper(HWND);
39+
40+
// SAFETY: it's a pointer behind a mutex. we'll live
41+
unsafe impl Send for KeyboardHook {}
42+
unsafe impl Sync for KeyboardHook {}
43+
44+
// SAFETY: ditto
45+
unsafe impl Send for HWNDWrapper {}
46+
unsafe impl Sync for HWNDWrapper {}
47+
48+
impl Drop for KeyboardHookHandle {
49+
fn drop(&mut self) {
50+
deinit_keyboard_hook(self.0);
51+
}
52+
}
2853

2954
// initialize keyboard hook
3055
// some DAWs (particularly Ableton) intercept incoming keyboard messages,
3156
// but we're naughty so we intercept them right back
32-
//
33-
// this is invoked by Window::open() since Rust doesn't have runtime static ctors
34-
pub(crate) fn init_keyboard_hook() {
35-
ONCE.call_once(|| {
36-
HOOK.lock().unwrap().hook = unsafe {
57+
pub(crate) fn init_keyboard_hook(hwnd: HWND) -> KeyboardHookHandle {
58+
// register hwnd to global window set
59+
OPEN_WINDOWS.write().unwrap().insert(HWNDWrapper(hwnd));
60+
61+
let hook = &mut *HOOK.lock().unwrap();
62+
63+
if hook.is_some() {
64+
// keyboard hook already exists, just return handle
65+
KeyboardHookHandle(HWNDWrapper(hwnd))
66+
} else {
67+
// keyboard hook doesn't exist (no windows open before this), create it
68+
let new_hook = KeyboardHook(unsafe {
3769
SetWindowsHookExA(
3870
WH_GETMESSAGE,
3971
Some(keyboard_hook_callback),
4072
GetModuleHandleA(ptr::null()),
4173
GetCurrentThreadId(),
4274
)
43-
};
44-
});
45-
}
75+
});
4676

47-
#[dtor]
48-
fn deinit_keyboard_hook() {
49-
let hook = HOOK.lock().unwrap();
77+
*hook = Some(new_hook);
5078

51-
if !hook.hook.is_null() {
52-
unsafe {
53-
UnhookWindowsHookEx(hook.hook);
54-
}
79+
KeyboardHookHandle(HWNDWrapper(hwnd))
5580
}
5681
}
5782

58-
struct WinKeyboardHook {
59-
hook: HHOOK,
60-
}
83+
fn deinit_keyboard_hook(hwnd: HWNDWrapper) {
84+
let windows = &mut *OPEN_WINDOWS.write().unwrap();
85+
86+
windows.remove(&hwnd);
6187

62-
impl WinKeyboardHook {
63-
const fn new() -> Self {
64-
Self { hook: ptr::null_mut() }
88+
if windows.is_empty() {
89+
if let Ok(Some(hook)) = HOOK.lock().as_deref() {
90+
unsafe {
91+
UnhookWindowsHookEx(hook.0);
92+
}
93+
}
6594
}
6695
}
6796

68-
// SAFETY: it's a pointer behind a mutex. we'll live
69-
unsafe impl Send for WinKeyboardHook {}
70-
unsafe impl Sync for WinKeyboardHook {}
71-
7297
unsafe extern "system" fn keyboard_hook_callback(
7398
n_code: c_int, wparam: WPARAM, lparam: LPARAM,
7499
) -> isize {
@@ -91,7 +116,7 @@ unsafe extern "system" fn keyboard_hook_callback(
91116
}
92117

93118
// check if `msg` is a keyboard message addressed
94-
// to a baseview window, and intercept it if so
119+
// to a window in OPEN_WINDOWS, and intercept it if so
95120
unsafe fn offer_message_to_baseview(msg: *mut MSG) -> bool {
96121
let msg = &*msg;
97122

@@ -102,21 +127,11 @@ unsafe fn offer_message_to_baseview(msg: *mut MSG) -> bool {
102127
_ => return false,
103128
}
104129

105-
// check if this is a baseview window (gross)
106-
let mut classname = [0u8; 9];
130+
// check if this is one of our windows. if so, intercept it
131+
if OPEN_WINDOWS.read().unwrap().contains(&HWNDWrapper(msg.hwnd)) {
132+
let _ = wnd_proc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
107133

108-
// SAFETY: It's Probably ASCII Lmao
109-
if GetClassNameA(msg.hwnd, &mut classname as *mut u8 as *mut i8, 9) != 0 {
110-
if &classname[0..8] == "Baseview".as_bytes() {
111-
let _ = wnd_proc(
112-
msg.hwnd,
113-
msg.message,
114-
msg.wParam,
115-
msg.lParam,
116-
);
117-
118-
return true;
119-
}
134+
return true;
120135
}
121136

122137
false

src/win/window.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use raw_window_handle::{
3333

3434
const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1;
3535

36-
use crate::win::hook;
36+
use crate::win::hook::{self, KeyboardHookHandle};
3737
use crate::{
3838
Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
3939
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
@@ -508,6 +508,11 @@ pub(super) struct WindowState {
508508
scale_policy: WindowScalePolicy,
509509
dw_style: u32,
510510

511+
// handle to the win32 keyboard hook
512+
// we don't need to read from this, just carry it around so the Drop impl can run
513+
#[allow(dead_code)]
514+
kb_hook: KeyboardHookHandle,
515+
511516
/// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably
512517
/// borrowing the fields from `WindowState` more than once. For instance, when the window
513518
/// handler requests a resize in response to a keyboard event, the window state will already be
@@ -687,15 +692,13 @@ impl Window<'_> {
687692
);
688693
// todo: manage error ^
689694

690-
hook::init_keyboard_hook();
695+
let kb_hook = hook::init_keyboard_hook(hwnd);
691696

692697
#[cfg(feature = "opengl")]
693-
let gl_context: Option<GlContext> = options
694-
.gl_config
695-
.map(|gl_config| {
696-
let mut handle = Win32WindowHandle::empty();
697-
handle.hwnd = hwnd as *mut c_void;
698-
let handle = RawWindowHandle::Win32(handle);
698+
let gl_context: Option<GlContext> = options.gl_config.map(|gl_config| {
699+
let mut handle = Win32WindowHandle::empty();
700+
handle.hwnd = hwnd as *mut c_void;
701+
let handle = RawWindowHandle::Win32(handle);
699702

700703
GlContext::create(&handle, gl_config).expect("Could not create OpenGL context")
701704
});
@@ -721,6 +724,8 @@ impl Window<'_> {
721724

722725
deferred_tasks: RefCell::new(VecDeque::with_capacity(4)),
723726

727+
kb_hook,
728+
724729
#[cfg(feature = "opengl")]
725730
gl_context,
726731
});

0 commit comments

Comments
 (0)