Skip to content

Commit 0bc4978

Browse files
authored
Merge branch 'RustAudio:master' into x11-gl-context-leak
2 parents 0a7e03c + 101c864 commit 0bc4978

File tree

4 files changed

+161
-5
lines changed

4 files changed

+161
-5
lines changed

src/win/drop_target.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,22 @@ use super::WindowState;
2727
// These function pointers have to be stored in a (const) variable before they can be transmuted
2828
// Transmuting is needed because winapi has a bug where the pt parameter has an incorrect
2929
// type `*const POINTL`
30+
#[allow(non_snake_case)]
3031
const DRAG_ENTER_PTR: unsafe extern "system" fn(
3132
this: *mut IDropTarget,
3233
pDataObj: *const IDataObject,
3334
grfKeyState: DWORD,
3435
pt: POINTL,
3536
pdwEffect: *mut DWORD,
3637
) -> HRESULT = DropTarget::drag_enter;
38+
#[allow(non_snake_case)]
3739
const DRAG_OVER_PTR: unsafe extern "system" fn(
3840
this: *mut IDropTarget,
3941
grfKeyState: DWORD,
4042
pt: POINTL,
4143
pdwEffect: *mut DWORD,
4244
) -> HRESULT = DropTarget::drag_over;
45+
#[allow(non_snake_case)]
4346
const DROP_PTR: unsafe extern "system" fn(
4447
this: *mut IDropTarget,
4548
pDataObj: *const IDataObject,

src/win/hook.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
use std::{
2+
collections::HashSet,
3+
ffi::c_int,
4+
ptr,
5+
sync::{LazyLock, RwLock},
6+
};
7+
8+
use winapi::{
9+
shared::{
10+
minwindef::{LPARAM, WPARAM},
11+
windef::{HHOOK, HWND, POINT},
12+
},
13+
um::{
14+
libloaderapi::GetModuleHandleW,
15+
processthreadsapi::GetCurrentThreadId,
16+
winuser::{
17+
CallNextHookEx, SetWindowsHookExW, UnhookWindowsHookEx, HC_ACTION, MSG, PM_REMOVE,
18+
WH_GETMESSAGE, WM_CHAR, WM_KEYDOWN, WM_KEYUP, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
19+
WM_USER,
20+
},
21+
},
22+
};
23+
24+
use crate::win::wnd_proc;
25+
26+
// track all windows opened by this instance of baseview
27+
// we use an RwLock here since the vast majority of uses (event interceptions)
28+
// will only need to read from the HashSet
29+
static HOOK_STATE: LazyLock<RwLock<KeyboardHookState>> = LazyLock::new(|| RwLock::default());
30+
31+
pub(crate) struct KeyboardHookHandle(HWNDWrapper);
32+
33+
#[derive(Default)]
34+
struct KeyboardHookState {
35+
hook: Option<HHOOK>,
36+
open_windows: HashSet<HWNDWrapper>,
37+
}
38+
39+
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
40+
struct HWNDWrapper(HWND);
41+
42+
// SAFETY: it's a pointer behind an RwLock. we'll live
43+
unsafe impl Send for KeyboardHookState {}
44+
unsafe impl Sync for KeyboardHookState {}
45+
46+
// SAFETY: we never access the underlying HWND ourselves, just use it as a HashSet entry
47+
unsafe impl Send for HWNDWrapper {}
48+
unsafe impl Sync for HWNDWrapper {}
49+
50+
impl Drop for KeyboardHookHandle {
51+
fn drop(&mut self) {
52+
deinit_keyboard_hook(self.0);
53+
}
54+
}
55+
56+
// initialize keyboard hook
57+
// some DAWs (particularly Ableton) intercept incoming keyboard messages,
58+
// but we're naughty so we intercept them right back
59+
pub(crate) fn init_keyboard_hook(hwnd: HWND) -> KeyboardHookHandle {
60+
let state = &mut *HOOK_STATE.write().unwrap();
61+
62+
// register hwnd to global window set
63+
state.open_windows.insert(HWNDWrapper(hwnd));
64+
65+
if state.hook.is_some() {
66+
// keyboard hook already exists, just return handle
67+
KeyboardHookHandle(HWNDWrapper(hwnd))
68+
} else {
69+
// keyboard hook doesn't exist (no windows open before this), create it
70+
let new_hook = unsafe {
71+
SetWindowsHookExW(
72+
WH_GETMESSAGE,
73+
Some(keyboard_hook_callback),
74+
GetModuleHandleW(ptr::null()),
75+
GetCurrentThreadId(),
76+
)
77+
};
78+
79+
state.hook = Some(new_hook);
80+
81+
KeyboardHookHandle(HWNDWrapper(hwnd))
82+
}
83+
}
84+
85+
fn deinit_keyboard_hook(hwnd: HWNDWrapper) {
86+
let state = &mut *HOOK_STATE.write().unwrap();
87+
88+
state.open_windows.remove(&hwnd);
89+
90+
if state.open_windows.is_empty() {
91+
if let Some(hhook) = state.hook {
92+
unsafe {
93+
UnhookWindowsHookEx(hhook);
94+
}
95+
96+
state.hook = None;
97+
}
98+
}
99+
}
100+
101+
unsafe extern "system" fn keyboard_hook_callback(
102+
n_code: c_int, wparam: WPARAM, lparam: LPARAM,
103+
) -> isize {
104+
let msg = lparam as *mut MSG;
105+
106+
if n_code == HC_ACTION && wparam == PM_REMOVE as usize && offer_message_to_baseview(msg) {
107+
*msg = MSG {
108+
hwnd: ptr::null_mut(),
109+
message: WM_USER,
110+
wParam: 0,
111+
lParam: 0,
112+
time: 0,
113+
pt: POINT { x: 0, y: 0 },
114+
};
115+
116+
0
117+
} else {
118+
CallNextHookEx(ptr::null_mut(), n_code, wparam, lparam)
119+
}
120+
}
121+
122+
// check if `msg` is a keyboard message addressed to a window
123+
// in KeyboardHookState::open_windows, and intercept it if so
124+
unsafe fn offer_message_to_baseview(msg: *mut MSG) -> bool {
125+
let msg = &*msg;
126+
127+
// if this isn't a keyboard message, ignore it
128+
match msg.message {
129+
WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP | WM_CHAR | WM_SYSCHAR => {}
130+
131+
_ => return false,
132+
}
133+
134+
// check if this is one of our windows. if so, intercept it
135+
if HOOK_STATE.read().unwrap().open_windows.contains(&HWNDWrapper(msg.hwnd)) {
136+
let _ = wnd_proc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
137+
138+
return true;
139+
}
140+
141+
false
142+
}

src/win/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod cursor;
22
mod drop_target;
3+
mod hook;
34
mod keyboard;
45
mod window;
56

src/win/window.rs

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

3434
const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1;
3535

36+
use crate::win::hook::{self, KeyboardHookHandle};
3637
use crate::{
3738
Event, MouseButton, MouseCursor, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent,
3839
WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy,
@@ -118,7 +119,7 @@ impl Drop for ParentHandle {
118119
}
119120
}
120121

121-
unsafe extern "system" fn wnd_proc(
122+
pub(crate) unsafe extern "system" fn wnd_proc(
122123
hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM,
123124
) -> LRESULT {
124125
if msg == WM_CREATE {
@@ -507,6 +508,11 @@ pub(super) struct WindowState {
507508
scale_policy: WindowScalePolicy,
508509
dw_style: u32,
509510

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+
510516
/// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably
511517
/// borrowing the fields from `WindowState` more than once. For instance, when the window
512518
/// handler requests a resize in response to a keyboard event, the window state will already be
@@ -519,19 +525,19 @@ pub(super) struct WindowState {
519525
}
520526

521527
impl WindowState {
522-
pub(super) fn create_window(&self) -> Window {
528+
pub(super) fn create_window(&self) -> Window<'_> {
523529
Window { state: self }
524530
}
525531

526-
pub(super) fn window_info(&self) -> Ref<WindowInfo> {
532+
pub(super) fn window_info(&self) -> Ref<'_, WindowInfo> {
527533
self.window_info.borrow()
528534
}
529535

530-
pub(super) fn keyboard_state(&self) -> Ref<KeyboardState> {
536+
pub(super) fn keyboard_state(&self) -> Ref<'_, KeyboardState> {
531537
self.keyboard_state.borrow()
532538
}
533539

534-
pub(super) fn handler_mut(&self) -> RefMut<Option<Box<dyn WindowHandler>>> {
540+
pub(super) fn handler_mut(&self) -> RefMut<'_, Option<Box<dyn WindowHandler>>> {
535541
self.handler.borrow_mut()
536542
}
537543

@@ -686,6 +692,8 @@ impl Window<'_> {
686692
);
687693
// todo: manage error ^
688694

695+
let kb_hook = hook::init_keyboard_hook(hwnd);
696+
689697
#[cfg(feature = "opengl")]
690698
let gl_context: Option<GlContext> = options.gl_config.map(|gl_config| {
691699
let mut handle = Win32WindowHandle::empty();
@@ -716,6 +724,8 @@ impl Window<'_> {
716724

717725
deferred_tasks: RefCell::new(VecDeque::with_capacity(4)),
718726

727+
kb_hook,
728+
719729
#[cfg(feature = "opengl")]
720730
gl_context,
721731
});

0 commit comments

Comments
 (0)