use super::AccessibilityBridge;
use crate::core::ObjectId;
use std::collections::HashMap;
use std::sync::Mutex;
pub struct WindowsAccessibilityBridge {
names: Mutex<HashMap<ObjectId, String>>,
native_handles: Mutex<HashMap<ObjectId, usize>>,
}
impl WindowsAccessibilityBridge {
pub fn new() -> Self {
Self { names: Mutex::new(HashMap::new()), native_handles: Mutex::new(HashMap::new()) }
}
pub fn register_handle(&self, id: ObjectId, ptr: usize) {
if let Ok(mut handles) = self.native_handles.lock() {
handles.insert(id, ptr);
}
}
pub fn unregister_handle(&self, id: ObjectId) {
if let Ok(mut handles) = self.native_handles.lock() {
handles.remove(&id);
}
}
fn post_event(&self, id: ObjectId, event: u32) -> bool {
let ptr = match self.native_handles.lock() {
Ok(h) => h.get(&id).copied(),
Err(_) => return false,
};
let Some(hwnd_val) = ptr else { return false };
#[cfg(target_os = "windows")]
{
let hwnd = hwnd_val as winapi::shared::windef::HWND;
unsafe {
winapi::um::winuser::NotifyWinEvent(
event,
hwnd,
winapi::um::winuser::OBJID_CLIENT,
0,
);
}
}
#[cfg(not(target_os = "windows"))]
{
let _ = event;
}
true
}
}
impl Default for WindowsAccessibilityBridge {
fn default() -> Self {
Self::new()
}
}
impl AccessibilityBridge for WindowsAccessibilityBridge {
fn set_accessibility_name(&self, id: ObjectId, name: &str) {
if let Ok(mut names) = self.names.lock() {
names.insert(id, name.to_string());
}
}
fn accessibility_name(&self, id: ObjectId) -> Option<String> {
self.names.lock().ok().and_then(|names| names.get(&id).cloned())
}
fn notify_name_changed(&self, id: ObjectId) {
self.post_event(id, winapi::um::winuser::EVENT_OBJECT_NAMECHANGE);
}
fn notify_value_changed(&self, id: ObjectId) {
self.post_event(id, winapi::um::winuser::EVENT_OBJECT_VALUECHANGE);
}
fn notify_state_changed(&self, id: ObjectId) {
self.post_event(id, winapi::um::winuser::EVENT_OBJECT_STATECHANGE);
}
fn notify_focus_changed(&self, id: ObjectId) {
self.post_event(id, winapi::um::winuser::EVENT_OBJECT_FOCUS);
}
}
#[cfg(target_os = "windows")]
pub mod uia_constants {
pub const UIA_BUTTON_CONTROL_TYPE: u32 = 50000u32;
pub const UIA_CHECKBOX_CONTROL_TYPE: u32 = 50001u32;
pub const UIA_COMBOBOX_CONTROL_TYPE: u32 = 50003u32;
pub const UIA_EDIT_CONTROL_TYPE: u32 = 50004u32;
pub const UIA_GROUP_CONTROL_TYPE: u32 = 50008u32;
pub const UIA_IMAGE_CONTROL_TYPE: u32 = 50009u32;
pub const UIA_LISTITEM_CONTROL_TYPE: u32 = 50010u32;
pub const UIA_LIST_CONTROL_TYPE: u32 = 50011u32;
pub const UIA_MENU_CONTROL_TYPE: u32 = 50012u32;
pub const UIA_MENUBAR_CONTROL_TYPE: u32 = 50013u32;
pub const UIA_MENUITEM_CONTROL_TYPE: u32 = 50014u32;
pub const UIA_PROGRESSBAR_CONTROL_TYPE: u32 = 50016u32;
pub const UIA_RADIOBUTTON_CONTROL_TYPE: u32 = 50017u32;
pub const UIA_SCROLLBAR_CONTROL_TYPE: u32 = 50018u32;
pub const UIA_SLIDER_CONTROL_TYPE: u32 = 50019u32;
pub const UIA_SPINBUTTON_CONTROL_TYPE: u32 = 50020u32;
pub const UIA_STATUSBAR_CONTROL_TYPE: u32 = 50021u32;
pub const UIA_TAB_CONTROL_TYPE: u32 = 50022u32;
pub const UIA_TABLE_CONTROL_TYPE: u32 = 50023u32;
pub const UIA_TEXT_CONTROL_TYPE: u32 = 50024u32;
pub const UIA_TOOLTIP_CONTROL_TYPE: u32 = 50026u32;
pub const UIA_TREE_CONTROL_TYPE: u32 = 50028u32;
pub const UIA_WINDOW_CONTROL_TYPE: u32 = 50031u32;
pub const UIA_HYPERLINK_CONTROL_TYPE: u32 = 50032u32;
pub const UIA_DATAGRID_CONTROL_TYPE: u32 = 50035u32;
}
#[cfg(target_os = "windows")]
pub fn uia_control_type_id(role: &super::A11yRole) -> u32 {
use uia_constants::*;
match role {
super::A11yRole::Button | super::A11yRole::Switch => UIA_BUTTON_CONTROL_TYPE,
super::A11yRole::Label | super::A11yRole::Heading | super::A11yRole::Paragraph => {
UIA_TEXT_CONTROL_TYPE
}
super::A11yRole::TextField => UIA_EDIT_CONTROL_TYPE,
super::A11yRole::CheckBox => UIA_CHECKBOX_CONTROL_TYPE,
super::A11yRole::RadioButton => UIA_RADIOBUTTON_CONTROL_TYPE,
super::A11yRole::Slider => UIA_SLIDER_CONTROL_TYPE,
super::A11yRole::ProgressBar => UIA_PROGRESSBAR_CONTROL_TYPE,
super::A11yRole::List => UIA_LIST_CONTROL_TYPE,
super::A11yRole::Table => UIA_TABLE_CONTROL_TYPE,
super::A11yRole::Image => UIA_IMAGE_CONTROL_TYPE,
super::A11yRole::Link => UIA_HYPERLINK_CONTROL_TYPE,
super::A11yRole::Group | super::A11yRole::StatusBar => UIA_GROUP_CONTROL_TYPE,
super::A11yRole::Window => UIA_WINDOW_CONTROL_TYPE,
super::A11yRole::Dialog | super::A11yRole::Alert => UIA_WINDOW_CONTROL_TYPE,
super::A11yRole::Menu => UIA_MENU_CONTROL_TYPE,
super::A11yRole::MenuItem => UIA_MENUITEM_CONTROL_TYPE,
super::A11yRole::Tab => UIA_TAB_CONTROL_TYPE,
super::A11yRole::ComboBox => UIA_COMBOBOX_CONTROL_TYPE,
super::A11yRole::SpinButton => UIA_SPINBUTTON_CONTROL_TYPE,
super::A11yRole::ToolTip => UIA_TOOLTIP_CONTROL_TYPE,
super::A11yRole::Tree => UIA_TREE_CONTROL_TYPE,
super::A11yRole::Unknown => UIA_GROUP_CONTROL_TYPE,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bridge_send_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<WindowsAccessibilityBridge>();
assert_sync::<WindowsAccessibilityBridge>();
}
#[test]
fn test_uia_control_type_mapping() {
#[cfg(target_os = "windows")]
{
assert_eq!(
uia_control_type_id(&super::A11yRole::Button),
uia_constants::UIA_BUTTON_CONTROL_TYPE
);
assert_eq!(
uia_control_type_id(&super::A11yRole::TextField),
uia_constants::UIA_EDIT_CONTROL_TYPE
);
assert_eq!(
uia_control_type_id(&super::A11yRole::Window),
uia_constants::UIA_WINDOW_CONTROL_TYPE
);
}
}
}