Skip to main content

accesskit_windows/
subclass.rs

1// Copyright 2022 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6use accesskit::{ActionHandler, ActivationHandler, TreeUpdate};
7use std::{
8    cell::{Cell, RefCell},
9    ffi::c_void,
10    mem::transmute,
11};
12use windows::{
13    core::*,
14    Win32::{Foundation::*, UI::WindowsAndMessaging::*},
15};
16
17use crate::{Adapter, QueuedEvents};
18
19fn win32_error() -> ! {
20    panic!("{}", Error::from_thread())
21}
22
23// Work around a difference between the SetWindowLongPtrW API definition
24// in windows-rs on 32-bit and 64-bit Windows.
25#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
26type LongPtr = isize;
27#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
28type LongPtr = i32;
29
30const PROP_NAME: PCWSTR = w!("AccessKitAdapter");
31
32struct SubclassState {
33    adapter: Adapter,
34    activation_handler: Box<dyn ActivationHandler>,
35}
36
37struct SubclassImpl {
38    hwnd: HWND,
39    state: RefCell<SubclassState>,
40    prev_wnd_proc: WNDPROC,
41    window_destroyed: Cell<bool>,
42}
43
44extern "system" fn wnd_proc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
45    let handle = unsafe { GetPropW(window, PROP_NAME) };
46    let impl_ptr = handle.0 as *const SubclassImpl;
47    assert!(!impl_ptr.is_null());
48    let r#impl = unsafe { &*impl_ptr };
49    match message {
50        WM_GETOBJECT => {
51            let mut state = r#impl.state.borrow_mut();
52            let state_mut = &mut *state;
53            if let Some(result) = state_mut.adapter.handle_wm_getobject(
54                wparam,
55                lparam,
56                &mut *state_mut.activation_handler,
57            ) {
58                drop(state);
59                return result.into();
60            }
61        }
62        WM_SETFOCUS | WM_EXITMENULOOP | WM_EXITSIZEMOVE => {
63            r#impl.update_window_focus_state(true);
64        }
65        WM_KILLFOCUS | WM_ENTERMENULOOP | WM_ENTERSIZEMOVE => {
66            r#impl.update_window_focus_state(false);
67        }
68        WM_NCDESTROY => {
69            r#impl.window_destroyed.set(true);
70        }
71        _ => (),
72    }
73    unsafe { CallWindowProcW(r#impl.prev_wnd_proc, window, message, wparam, lparam) }
74}
75
76impl SubclassImpl {
77    fn new(
78        hwnd: HWND,
79        activation_handler: impl 'static + ActivationHandler,
80        action_handler: impl 'static + ActionHandler + Send,
81    ) -> Box<Self> {
82        let adapter = Adapter::new(hwnd, false, action_handler);
83        let state = RefCell::new(SubclassState {
84            adapter,
85            activation_handler: Box::new(activation_handler),
86        });
87        Box::new(Self {
88            hwnd,
89            state,
90            prev_wnd_proc: None,
91            window_destroyed: Cell::new(false),
92        })
93    }
94
95    fn install(&mut self) {
96        if !unsafe { GetPropW(self.hwnd, PROP_NAME) }.0.is_null() {
97            panic!(
98                "subclassing adapter already instantiated on window {:?}",
99                self.hwnd.0
100            );
101        }
102        unsafe {
103            SetPropW(
104                self.hwnd,
105                PROP_NAME,
106                Some(HANDLE(self as *const SubclassImpl as _)),
107            )
108        }
109        .unwrap();
110        let result =
111            unsafe { SetWindowLongPtrW(self.hwnd, GWLP_WNDPROC, wnd_proc as *const c_void as _) };
112        if result == 0 {
113            win32_error();
114        }
115        self.prev_wnd_proc = unsafe { transmute::<LongPtr, WNDPROC>(result) };
116    }
117
118    fn update_window_focus_state(&self, is_focused: bool) {
119        let mut state = self.state.borrow_mut();
120        if let Some(events) = state.adapter.update_window_focus_state(is_focused) {
121            drop(state);
122            events.raise();
123        }
124    }
125
126    fn uninstall(&self) {
127        if self.window_destroyed.get() {
128            return;
129        }
130        let result = unsafe {
131            SetWindowLongPtrW(
132                self.hwnd,
133                GWLP_WNDPROC,
134                transmute::<WNDPROC, LongPtr>(self.prev_wnd_proc),
135            )
136        };
137        if result == 0 {
138            win32_error();
139        }
140        unsafe { RemovePropW(self.hwnd, PROP_NAME) }.unwrap();
141    }
142}
143
144/// Uses [Win32 subclassing] to handle `WM_GETOBJECT` messages on a window
145/// that provides no other way of adding custom message handlers.
146///
147/// [Win32 subclassing]: https://docs.microsoft.com/en-us/windows/win32/controls/subclassing-overview
148pub struct SubclassingAdapter(Box<SubclassImpl>);
149
150impl SubclassingAdapter {
151    /// Creates a new Windows platform adapter using window subclassing.
152    /// This must be done before the window is shown or focused
153    /// for the first time.
154    ///
155    /// This must be called on the thread that owns the window. The activation
156    /// handler will always be called on that thread. The action handler
157    /// may or may not be called on that thread.
158    ///
159    /// # Panics
160    ///
161    /// Panics if the window is already visible.
162    pub fn new(
163        hwnd: HWND,
164        activation_handler: impl 'static + ActivationHandler,
165        action_handler: impl 'static + ActionHandler + Send,
166    ) -> Self {
167        if unsafe { IsWindowVisible(hwnd) }.into() {
168            panic!("The AccessKit Windows subclassing adapter must be created before the window is shown (made visible) for the first time.");
169        }
170
171        let mut r#impl = SubclassImpl::new(hwnd, activation_handler, action_handler);
172        r#impl.install();
173        Self(r#impl)
174    }
175
176    /// If and only if the tree has been initialized, call the provided function
177    /// and apply the resulting update. Note: If the caller's implementation of
178    /// [`ActivationHandler::request_initial_tree`] initially returned `None`,
179    /// the [`TreeUpdate`] returned by the provided function must contain
180    /// a full tree.
181    ///
182    /// If a [`QueuedEvents`] instance is returned, the caller must call
183    /// [`QueuedEvents::raise`] on it.
184    pub fn update_if_active(
185        &mut self,
186        update_factory: impl FnOnce() -> TreeUpdate,
187    ) -> Option<QueuedEvents> {
188        // SAFETY: We use `RefCell::borrow_mut` here, even though
189        // `RefCell::get_mut` is allowed (because this method takes
190        // a mutable self reference), just in case there's some way
191        // this method can be called from within the subclassed window
192        // procedure, e.g. via `ActivationHandler`.
193        let mut state = self.0.state.borrow_mut();
194        state.adapter.update_if_active(update_factory)
195    }
196}
197
198impl Drop for SubclassingAdapter {
199    fn drop(&mut self) {
200        self.0.uninstall();
201    }
202}