use crate::common::{Result, HWND};
use std::fmt::{Debug, Display, Formatter};
pub use windows::Win32::UI::{
Accessibility::{
ROLE_SYSTEM_ALERT, ROLE_SYSTEM_ANIMATION, ROLE_SYSTEM_APPLICATION, ROLE_SYSTEM_BORDER,
ROLE_SYSTEM_BUTTONDROPDOWN, ROLE_SYSTEM_BUTTONDROPDOWNGRID, ROLE_SYSTEM_BUTTONMENU,
ROLE_SYSTEM_CARET, ROLE_SYSTEM_CELL, ROLE_SYSTEM_CHARACTER, ROLE_SYSTEM_CHART,
ROLE_SYSTEM_CHECKBUTTON, ROLE_SYSTEM_CLIENT, ROLE_SYSTEM_CLOCK, ROLE_SYSTEM_COLUMN,
ROLE_SYSTEM_COLUMNHEADER, ROLE_SYSTEM_COMBOBOX, ROLE_SYSTEM_CURSOR, ROLE_SYSTEM_DIAGRAM,
ROLE_SYSTEM_DIAL, ROLE_SYSTEM_DIALOG, ROLE_SYSTEM_DOCUMENT, ROLE_SYSTEM_DROPLIST,
ROLE_SYSTEM_EQUATION, ROLE_SYSTEM_GRAPHIC, ROLE_SYSTEM_GRIP, ROLE_SYSTEM_GROUPING,
ROLE_SYSTEM_HELPBALLOON, ROLE_SYSTEM_HOTKEYFIELD, ROLE_SYSTEM_INDICATOR,
ROLE_SYSTEM_IPADDRESS, ROLE_SYSTEM_LINK, ROLE_SYSTEM_LIST, ROLE_SYSTEM_LISTITEM,
ROLE_SYSTEM_MENUBAR, ROLE_SYSTEM_MENUITEM, ROLE_SYSTEM_MENUPOPUP, ROLE_SYSTEM_OUTLINE,
ROLE_SYSTEM_OUTLINEBUTTON, ROLE_SYSTEM_OUTLINEITEM, ROLE_SYSTEM_PAGETAB,
ROLE_SYSTEM_PAGETABLIST, ROLE_SYSTEM_PANE, ROLE_SYSTEM_PROGRESSBAR,
ROLE_SYSTEM_PROPERTYPAGE, ROLE_SYSTEM_PUSHBUTTON, ROLE_SYSTEM_RADIOBUTTON, ROLE_SYSTEM_ROW,
ROLE_SYSTEM_ROWHEADER, ROLE_SYSTEM_SCROLLBAR, ROLE_SYSTEM_SEPARATOR, ROLE_SYSTEM_SLIDER,
ROLE_SYSTEM_SOUND, ROLE_SYSTEM_SPINBUTTON, ROLE_SYSTEM_SPLITBUTTON, ROLE_SYSTEM_STATICTEXT,
ROLE_SYSTEM_STATUSBAR, ROLE_SYSTEM_TABLE, ROLE_SYSTEM_TEXT, ROLE_SYSTEM_TITLEBAR,
ROLE_SYSTEM_TOOLBAR, ROLE_SYSTEM_TOOLTIP, ROLE_SYSTEM_WHITESPACE, ROLE_SYSTEM_WINDOW,
STATE_SYSTEM_HASPOPUP, STATE_SYSTEM_NORMAL,
},
WindowsAndMessaging::{
OBJID_ALERT, OBJID_CARET, OBJID_CLIENT, OBJID_CURSOR, OBJID_HSCROLL, OBJID_MENU,
OBJID_NATIVEOM, OBJID_QUERYCLASSNAMEIDX, OBJID_SIZEGRIP, OBJID_SOUND, OBJID_SYSMENU,
OBJID_TITLEBAR, OBJID_VSCROLL, OBJID_WINDOW,
},
};
use windows::{
core::{Error, Interface, Type, BSTR},
Win32::{
Foundation::{POINT, S_FALSE},
System::{Com::IDispatch, Variant::VARIANT},
UI::Accessibility::{
AccessibleChildren, AccessibleObjectFromEvent, AccessibleObjectFromPoint,
AccessibleObjectFromWindow, GetRoleTextW, GetStateTextW, IAccessible,
WindowFromAccessibleObject,
},
},
};
#[derive(Clone)]
pub struct AccessibleObject(IAccessible, i32);
impl AccessibleObject {
pub(crate) fn from_raw(acc: IAccessible, child: i32) -> Self {
Self(acc, child)
}
pub fn get_raw(&self) -> &IAccessible {
&self.0
}
pub fn get_child_id(&self) -> i32 {
self.1
}
pub fn from_window(h_wnd: HWND) -> Result<Self> {
let acc = unsafe {
let mut p_acc = std::mem::zeroed();
if let Err(e) = AccessibleObjectFromWindow(
h_wnd,
OBJID_WINDOW.0 as u32,
&IAccessible::IID,
&mut p_acc,
) {
return Err(e);
}
match IAccessible::from_abi(p_acc) {
Err(e) => return Err(e),
Ok(r) => r,
}
};
Ok(Self(acc, 0))
}
pub fn from_caret() -> Result<Self> {
let acc = unsafe {
let mut p_acc = std::mem::zeroed();
if let Err(e) = AccessibleObjectFromWindow(
Default::default(),
OBJID_CARET.0 as u32,
&IAccessible::IID,
&mut p_acc,
) {
return Err(e);
}
match IAccessible::from_abi(p_acc) {
Err(e) => return Err(e),
Ok(r) => r,
}
};
Ok(Self(acc, 0))
}
pub fn from_point(x: i32, y: i32) -> Result<(Self, i32)> {
let acc = unsafe {
let mut p_acc: Option<IAccessible> = None;
let point = POINT { x, y };
let mut var = Default::default();
AccessibleObjectFromPoint(point, &mut p_acc, &mut var)?;
match p_acc {
None => {
return Err(Error::new(
S_FALSE,
&format!("Can't obtain the accessible object at ({}, {}).", x, y),
));
}
Some(r) => (r, i32::try_from(&var).unwrap_or(0)),
}
};
Ok((Self(acc.0, acc.1), acc.1))
}
pub fn from_event(h_wnd: HWND, id: i32, child_id: i32) -> Result<(Self, i32)> {
let acc = unsafe {
let mut p_acc = std::mem::zeroed();
let mut var = std::mem::zeroed();
if let Err(e) =
AccessibleObjectFromEvent(h_wnd, id as u32, child_id as u32, &mut p_acc, &mut var)
{
return Err(e);
}
match p_acc {
None => {
return Err(Error::new(
S_FALSE,
&format!(
"Can't obtain the accessible object, the h_wnd is {:?}.",
h_wnd.0
),
));
}
Some(r) => (r, i32::try_from(&var).unwrap_or(0)),
}
};
Ok((Self(acc.0, acc.1), acc.1))
}
pub fn get_name(&self, child: i32) -> String {
unsafe { self.0.get_accName(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_description(&self, child: i32) -> String {
unsafe { self.0.get_accDescription(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_help(&self, child: i32) -> String {
unsafe { self.0.get_accHelp(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_help_topic(&self, child: i32) -> (String, i32) {
unsafe {
let mut help_file = BSTR::new();
let id_topic = self
.0
.get_accHelpTopic(&mut help_file, &VARIANT::from(child))
.unwrap_or(0);
(help_file.to_string(), id_topic)
}
}
pub fn get_keyboard_shortcut(&self, child: i32) -> String {
unsafe { self.0.get_accKeyboardShortcut(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_value(&self, child: i32) -> String {
unsafe { self.0.get_accValue(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_default_action(&self, child: i32) -> String {
unsafe { self.0.get_accDefaultAction(&VARIANT::from(child)) }
.unwrap_or(BSTR::new())
.to_string()
}
pub fn get_role(&self, child: i32) -> u32 {
unsafe {
if let Ok(v) = self.0.get_accRole(&VARIANT::from(child)) {
return u32::try_from(&v).unwrap_or(0);
} else {
0
}
}
}
pub fn get_role_text(&self, child: i32) -> String {
let role = self.get_role(child);
let mut text: [u16; 32] = [0; 32];
let len = unsafe { GetRoleTextW(role, Some(&mut text)) };
String::from_utf16_lossy(&text[..len as usize])
}
pub fn get_state_text(&self, child: i32) -> String {
let state = self.get_state(child);
let mut text: [u16; 32] = [0; 32];
let len = unsafe { GetStateTextW(state, Some(&mut text)) };
String::from_utf16_lossy(&text[..len as usize])
}
pub fn get_state(&self, child: i32) -> u32 {
unsafe {
if let Ok(v) = self.0.get_accState(&VARIANT::from(child)) {
return u32::try_from(&v).unwrap_or(0);
} else {
0
}
}
}
pub fn do_default_action(&self, child: i32) {
unsafe {
self.0
.accDoDefaultAction(&VARIANT::from(child))
.unwrap_or(());
}
}
pub fn select(&self, flags: i32, child: i32) {
unsafe {
self.0.accSelect(flags, &VARIANT::from(child)).unwrap_or(());
}
}
pub fn navigate(&self, nav_dir: i32, start: i32) -> Option<Self> {
unsafe {
let Ok(v) = self.0.accNavigate(nav_dir, &VARIANT::from(start)) else {
return None;
};
let Ok(d) = IDispatch::try_from(&v) else {
return match i32::try_from(&v) {
Ok(d) => Some(Self::from_raw(self.0.clone(), d)),
Err(_) => None,
};
};
Some(Self::from_raw(d.cast().unwrap(), 0))
}
}
pub fn hit_test(&self, left: i32, top: i32) -> Option<Self> {
unsafe {
let Ok(v) = self.0.accHitTest(left, top) else {
return None;
};
if let Ok(d) = IDispatch::try_from(&v) {
return Some(Self::from_raw(d.cast().unwrap(), 0));
}
Some(Self::from_raw(
self.0.clone(),
i32::try_from(&v).unwrap_or(0),
))
}
}
pub fn focus(&self) -> Option<Self> {
unsafe {
let Ok(v) = self.0.accFocus() else {
return None;
};
if let Ok(d) = IDispatch::try_from(&v) {
return Some(Self::from_raw(d.cast().unwrap(), 0));
}
Some(Self::from_raw(
self.0.clone(),
i32::try_from(&v).unwrap_or(0),
))
}
}
pub fn selection(&self) -> Option<Self> {
unsafe {
let Ok(v) = self.0.accSelection() else {
return None;
};
if let Ok(d) = IDispatch::try_from(&v) {
return Some(Self::from_raw(d.cast().unwrap(), 0));
}
Some(Self::from_raw(
self.0.clone(),
i32::try_from(&v).unwrap_or(0),
))
}
}
pub fn parent(&self) -> Option<Self> {
unsafe {
if let Ok(r) = self.0.accParent() {
Some(Self::from_raw(r.cast().unwrap(), 0))
} else {
None
}
}
}
pub fn child_count(&self) -> u32 {
unsafe {
if let Ok(r) = self.0.accChildCount() {
return r as u32;
}
}
0
}
pub fn get_child(&self, child: i32) -> Result<Self> {
unsafe {
match self.0.get_accChild(&VARIANT::from(child)) {
Err(e) => Err(e),
Ok(o) => Ok(Self::from_raw(o.cast().unwrap(), 0)),
}
}
}
pub fn children(&self, start: u32, count: u32) -> Result<Vec<Self>> {
unsafe {
let mut arr = vec![];
for _ in 0..count {
arr.push(VARIANT::default());
}
let mut cnt = std::mem::zeroed();
match AccessibleChildren(&self.0, start as i32, &mut arr, &mut cnt) {
Err(e) => Err(e),
Ok(_) => {
let mut v = vec![];
for i in 0..cnt {
if let Ok(d) = IDispatch::try_from(&arr[i as usize]) {
v.push(Self::from_raw(d.cast().unwrap(), 0));
}
if let Ok(d) = i32::try_from(&arr[i as usize]) {
v.push(Self::from_raw(self.0.clone(), d));
}
}
Ok(v)
}
}
}
}
pub fn location(&self, child: i32) -> (i32, i32, i32, i32) {
unsafe {
let (mut left, mut top, mut width, mut height) = (0i32, 0i32, 0i32, 0i32);
if let Ok(_) = self.0.accLocation(
&mut left,
&mut top,
&mut width,
&mut height,
&VARIANT::from(child),
) {
(left, top, width, height)
} else {
(0, 0, 0, 0)
}
}
}
#[allow(unused_variables)]
pub fn put_name(&self, child: i32, name: String) {
unreachable!()
}
pub fn put_value(&self, child: i32, value: String) {
unsafe {
self.0
.put_accValue(&VARIANT::from(child), &BSTR::from(value.as_str()))
.unwrap_or(());
}
}
pub fn window(&self) -> HWND {
let mut h_wnd = HWND::default();
unsafe {
WindowFromAccessibleObject(&self.0, Some(&mut h_wnd)).unwrap_or(());
}
h_wnd
}
}
impl Debug for AccessibleObject {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Display::fmt(self, f)
}
}
impl Display for AccessibleObject {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"AccessibleObject(name:{}, description:{}, role:{})",
self.get_name(self.1),
self.get_description(self.1),
self.get_role_text(self.1)
)
}
}
unsafe impl Sync for AccessibleObject {}
unsafe impl Send for AccessibleObject {}