Skip to content

Commit

Permalink
refactor(platforms/windows)!: Refactor window subclassing to avoid li…
Browse files Browse the repository at this point in the history
…fetime issue (#120)
  • Loading branch information
mwcampbell authored Jul 22, 2022
1 parent 27d5c78 commit 37579aa
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 50 deletions.
9 changes: 4 additions & 5 deletions platforms/windows/examples/winit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use accesskit::{
Action, ActionHandler, ActionRequest, Node, NodeId, Role, StringEncoding, Tree, TreeUpdate,
};
use accesskit_windows::{Adapter, WindowSubclass};
use accesskit_windows::{Adapter, SubclassingAdapter};
use std::{
num::NonZeroU128,
sync::{Arc, Mutex},
Expand Down Expand Up @@ -94,20 +94,19 @@ fn main() {
let adapter = {
let state = Arc::clone(&state);
let proxy = Mutex::new(event_loop.create_proxy());
Arc::new(Adapter::new(
Adapter::new(
HWND(window.hwnd() as _),
Box::new(move || {
let state = state.lock().unwrap();
initial_tree_update(&state)
}),
Box::new(WinitActionHandler(proxy)),
))
)
};
let _subclass = WindowSubclass::new(&*adapter);
let adapter = SubclassingAdapter::new(adapter);

window.set_visible(true);

let adapter = Arc::clone(&adapter); // to move into the event handler
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;

Expand Down
2 changes: 1 addition & 1 deletion platforms/windows/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod adapter;
pub use adapter::{Adapter, QueuedEvents};

mod subclass;
pub use subclass::WindowSubclass;
pub use subclass::SubclassingAdapter;

#[cfg(test)]
mod tests;
113 changes: 74 additions & 39 deletions platforms/windows/src/subclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// the LICENSE-APACHE file) or the MIT license (found in
// the LICENSE-MIT file), at your option.

use std::{cell::Cell, ffi::c_void, mem::transmute};
use std::{cell::Cell, ffi::c_void, mem::transmute, ops::Deref};
use windows::{
core::*,
Win32::{Foundation::*, UI::WindowsAndMessaging::*},
Expand All @@ -13,71 +13,106 @@ use crate::Adapter;

const PROP_NAME: &str = "AccessKitAdapter";

struct SubclassData<'a> {
adapter: &'a Adapter,
struct SubclassImpl {
adapter: Adapter,
prev_wnd_proc: WNDPROC,
window_destroyed: Cell<bool>,
}

extern "system" fn wnd_proc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
let handle = unsafe { GetPropW(window, PROP_NAME) };
let data_ptr = handle.0 as *const SubclassData;
assert!(!data_ptr.is_null());
let data = unsafe { &*data_ptr };
let impl_ptr = handle.0 as *const SubclassImpl;
assert!(!impl_ptr.is_null());
let r#impl = unsafe { &*impl_ptr };
if message == WM_GETOBJECT {
if let Some(result) = data.adapter.handle_wm_getobject(wparam, lparam) {
if let Some(result) = r#impl.adapter.handle_wm_getobject(wparam, lparam) {
return result.into();
}
}
if message == WM_NCDESTROY {
data.window_destroyed.set(true);
r#impl.window_destroyed.set(true);
}
unsafe { CallWindowProcW(data.prev_wnd_proc, window, message, wparam, lparam) }
unsafe { CallWindowProcW(r#impl.prev_wnd_proc, window, message, wparam, lparam) }
}

/// Uses [Win32 subclassing] to handle `WM_GETOBJECT` messages on a window
/// that provides no other way of adding custom message handlers.
///
/// [Win32 subclassing]: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
#[repr(transparent)]
pub struct WindowSubclass<'a>(Box<SubclassData<'a>>);

impl<'a> WindowSubclass<'a> {
pub fn new(adapter: &'a Adapter) -> Self {
let hwnd = adapter.window_handle();
let mut data = Box::new(SubclassData {
impl SubclassImpl {
fn new(adapter: Adapter) -> Box<Self> {
Box::new(Self {
adapter,
prev_wnd_proc: None,
window_destroyed: Cell::new(false),
});
unsafe { SetPropW(hwnd, PROP_NAME, HANDLE(&*data as *const SubclassData as _)) }.unwrap();
})
}

fn install(&mut self) {
let hwnd = self.adapter.window_handle();
unsafe { SetPropW(hwnd, PROP_NAME, HANDLE(self as *const SubclassImpl as _)) }.unwrap();
let result =
unsafe { SetWindowLongPtrW(hwnd, GWLP_WNDPROC, wnd_proc as *const c_void as _) };
if result == 0 {
let result: Result<()> = Err(Error::from_win32());
result.unwrap();
}
data.prev_wnd_proc = unsafe { transmute::<isize, WNDPROC>(result) };
Self(data)
self.prev_wnd_proc = unsafe { transmute::<isize, WNDPROC>(result) };
}

fn uninstall(&self) {
if self.window_destroyed.get() {
return;
}
let hwnd = self.adapter.window_handle();
let result = unsafe {
SetWindowLongPtrW(
hwnd,
GWLP_WNDPROC,
transmute::<WNDPROC, isize>(self.prev_wnd_proc),
)
};
if result == 0 {
let result: Result<()> = Err(Error::from_win32());
result.unwrap();
}
unsafe { RemovePropW(hwnd, PROP_NAME) }.unwrap();
}
}

/// Uses [Win32 subclassing] to handle `WM_GETOBJECT` messages on a window
/// that provides no other way of adding custom message handlers.
///
/// [Win32 subclassing]: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
#[repr(transparent)]
pub struct SubclassingAdapter(Option<Box<SubclassImpl>>);

impl SubclassingAdapter {
pub fn new(adapter: Adapter) -> Self {
let mut r#impl = SubclassImpl::new(adapter);
r#impl.install();
Self(Some(r#impl))
}

pub fn inner(&self) -> &Adapter {
&self.0.as_ref().unwrap().adapter
}

pub fn into_inner(mut self) -> Adapter {
let r#impl = self.0.take().unwrap();
r#impl.uninstall();
r#impl.adapter
}
}

impl Deref for SubclassingAdapter {
type Target = Adapter;

fn deref(&self) -> &Adapter {
self.inner()
}
}

impl Drop for WindowSubclass<'_> {
impl Drop for SubclassingAdapter {
fn drop(&mut self) {
if !self.0.window_destroyed.get() {
let hwnd = self.0.adapter.window_handle();
let result = unsafe {
SetWindowLongPtrW(
hwnd,
GWLP_WNDPROC,
transmute::<WNDPROC, isize>(self.0.prev_wnd_proc),
)
};
if result == 0 {
let result: Result<()> = Err(Error::from_win32());
result.unwrap();
}
unsafe { RemovePropW(hwnd, PROP_NAME) }.unwrap();
if let Some(r#impl) = self.0.as_ref() {
r#impl.uninstall();
}
}
}
10 changes: 5 additions & 5 deletions platforms/windows/src/tests/subclassed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use winit::{
window::WindowBuilder,
};

use crate::{Adapter, WindowSubclass};
use crate::{Adapter, SubclassingAdapter};

const WINDOW_TITLE: &str = "Simple test";

Expand Down Expand Up @@ -71,12 +71,12 @@ fn has_native_uia() {
Box::new(get_initial_state),
Box::new(NullActionHandler {}),
);
let subclass = WindowSubclass::new(&adapter);
let adapter = SubclassingAdapter::new(adapter);
assert!(unsafe { UiaHasServerSideProvider(hwnd) }.as_bool());
drop(subclass);
let adapter = adapter.into_inner(); // stops subclassing
assert!(!unsafe { UiaHasServerSideProvider(hwnd) }.as_bool());
let subclass = WindowSubclass::new(&adapter);
let adapter = SubclassingAdapter::new(adapter);
assert!(unsafe { UiaHasServerSideProvider(hwnd) }.as_bool());
drop(window);
drop(subclass);
drop(adapter);
}

0 comments on commit 37579aa

Please sign in to comment.