accessibility_ng/
observer.rs

1use std::{ffi::c_void, str::FromStr};
2
3use accessibility_sys_ng::{
4    pid_t, AXObserverAddNotification, AXObserverCallback, AXObserverCallbackWithInfo,
5    AXObserverCreate, AXObserverCreateWithInfoCallback, AXObserverGetRunLoopSource,
6    AXObserverGetTypeID, AXObserverRef, AXObserverRemoveNotification, AXUIElementRef,
7};
8
9use crate::{
10    util::{ax_call, ax_call_void},
11    Error,
12};
13
14use core_foundation::{
15    base::TCFType,
16    declare_TCFType, impl_CFTypeDescription, impl_TCFType,
17    runloop::{kCFRunLoopDefaultMode, CFRunLoop},
18    string::CFString,
19};
20
21use super::AXUIElement;
22
23declare_TCFType!(AXObserver, AXObserverRef);
24impl_TCFType!(AXObserver, AXObserverRef, AXObserverGetTypeID);
25impl_CFTypeDescription!(AXObserver);
26
27unsafe impl Send for AXObserver {}
28
29impl AXObserver {
30    pub fn new(pid: pid_t, callback: AXObserverCallback) -> Result<Self, Error> {
31        unsafe {
32            Ok(TCFType::wrap_under_create_rule(
33                ax_call(|x| AXObserverCreate(pid, callback, x)).map_err(Error::Ax)?,
34            ))
35        }
36    }
37
38    pub fn new_with_info(pid: pid_t, callback: AXObserverCallbackWithInfo) -> Result<Self, Error> {
39        unsafe {
40            Ok(TCFType::wrap_under_create_rule(
41                ax_call(|x| AXObserverCreateWithInfoCallback(pid, callback, x))
42                    .map_err(Error::Ax)?,
43            ))
44        }
45    }
46
47    pub fn new_from_bundle(bundle_id: &str, callback: AXObserverCallback) -> Result<Self, Error> {
48        let bundle_ui_element = AXUIElement::application_with_bundle(bundle_id)?;
49        let bundle_pid = bundle_ui_element.pid()?;
50        unsafe {
51            Ok(TCFType::wrap_under_create_rule(
52                ax_call(|x| AXObserverCreate(bundle_pid, callback, x)).map_err(Error::Ax)?,
53            ))
54        }
55    }
56
57    pub fn new_from_bundle_with_info(
58        bundle_id: &str,
59        callback: AXObserverCallbackWithInfo,
60    ) -> Result<Self, Error> {
61        let bundle_ui_element = AXUIElement::application_with_bundle(bundle_id)?;
62        let bundle_pid = bundle_ui_element.pid()?;
63        unsafe {
64            Ok(TCFType::wrap_under_create_rule(
65                ax_call(|x| AXObserverCreateWithInfoCallback(bundle_pid, callback, x))
66                    .map_err(Error::Ax)?,
67            ))
68        }
69    }
70
71    pub fn add_notification<T>(
72        &mut self,
73        notification: &str,
74        ui_element: &AXUIElement,
75        mut ctx: T,
76    ) -> Result<(), Error> {
77        unsafe {
78            // Create CFStringRef from notification string
79            let notification_cfstr = CFString::from_str(notification).unwrap();
80
81            Ok(ax_call_void(|| {
82                AXObserverAddNotification(
83                    self.0,
84                    ui_element.as_CFTypeRef() as AXUIElementRef,
85                    notification_cfstr.as_concrete_TypeRef(),
86                    &mut ctx as *mut _ as *mut c_void,
87                )
88            })
89            .map_err(Error::Ax)?)
90        }
91    }
92
93    pub fn remove_notification(
94        &mut self,
95        notification: &str,
96        ui_element: &AXUIElement,
97    ) -> Result<(), Error> {
98        unsafe {
99            // Create CFStringRef from notification string
100            let notification_cfstr = CFString::from_str(notification).unwrap();
101
102            Ok(ax_call_void(|| {
103                AXObserverRemoveNotification(
104                    self.0,
105                    ui_element.as_CFTypeRef() as AXUIElementRef,
106                    notification_cfstr.as_concrete_TypeRef(),
107                )
108            })
109            .map_err(Error::Ax)?)
110        }
111    }
112
113    pub fn start(&self) {
114        let runloop = CFRunLoop::get_current();
115        unsafe {
116            let source = TCFType::wrap_under_create_rule(AXObserverGetRunLoopSource(self.0));
117            runloop.add_source(&source, kCFRunLoopDefaultMode)
118        }
119    }
120
121    pub fn stop(&self) {
122        let runloop = CFRunLoop::get_current();
123        unsafe {
124            let source = TCFType::wrap_under_create_rule(AXObserverGetRunLoopSource(self.0));
125            runloop.remove_source(&source, kCFRunLoopDefaultMode)
126        }
127    }
128}
129
130// // This is WIP - I am trying to build an abstraction similar to the AXSwift project's implementation.
131// pub type Callback = fn(observer: &Observer, element: AXUIElement, notification: AXNotification);
132// pub struct Observer {
133//     ax_observer: AXObserver,
134//     pub callback: Callback,
135// }
136
137// impl Observer {
138//     pub fn new(process_id: pid_t, callback: Callback) -> Result<Self, Error> {
139//         let ax_observer = AXObserver::new(process_id, internal_callback)?;
140
141//         let callback = callback;
142
143//         ax_observer.start();
144
145//         Ok(Self {
146//             ax_observer,
147//             callback,
148//         })
149//     }
150
151//     pub fn add_notification(
152//         &mut self,
153//         notification: AXNotification,
154//         element: AXUIElement,
155//     ) -> Result<(), Error> {
156//         let self_ptr = std::ptr::addr_of!(self);
157//         self.ax_observer
158//             .add_notification(notification.to_string(), &element, &self_ptr)
159//     }
160
161//     pub fn remove_notification(
162//         &mut self,
163//         notification: AXNotification,
164//         element: AXUIElement,
165//     ) -> Result<(), Error> {
166//         self.ax_observer
167//             .remove_notification(notification.to_string(), &element)
168//     }
169
170//     pub fn start(&self) {
171//         self.ax_observer.start();
172//     }
173
174//     pub fn stop(&self) {
175//         self.ax_observer.stop();
176//     }
177// }
178
179// unsafe extern "C" fn internal_callback(
180//     _ax_observer_ref: AXObserverRef,
181//     ax_ui_element_ref: AXUIElementRef,
182//     notification_ref: CFStringRef,
183//     raw_info: *mut c_void,
184// ) {
185//     let observer_address: *const &mut Observer = mem::transmute(raw_info);
186//     let observer = &**(observer_address.as_ref()).unwrap();
187//     let ax_ui_element: AXUIElement = TCFType::wrap_under_get_rule(ax_ui_element_ref);
188
189//     let notification_string = CFString::wrap_under_get_rule(notification_ref).to_string();
190//     let notification: AXNotification =
191//         AXNotification::from_str(notification_string.as_str()).unwrap();
192
193//     (observer.callback)(observer, ax_ui_element, notification);
194// }