1use std::{
2 ffi::c_uchar,
3 hash::{Hash, Hasher},
4 thread,
5 time::{Duration, Instant},
6};
7
8use accessibility_sys_ng::{
9 kAXTrustedCheckOptionPrompt, pid_t, AXIsProcessTrusted, AXIsProcessTrustedWithOptions,
10 AXUIElementCopyActionNames, AXUIElementCopyAttributeNames, AXUIElementCopyAttributeValue,
11 AXUIElementCopyParameterizedAttributeNames, AXUIElementCopyParameterizedAttributeValue,
12 AXUIElementCreateApplication, AXUIElementCreateSystemWide, AXUIElementGetPid,
13 AXUIElementGetTypeID, AXUIElementIsAttributeSettable, AXUIElementPerformAction, AXUIElementRef,
14 AXUIElementSetAttributeValue, AXUIElementSetMessagingTimeout,
15};
16use cocoa::{
17 base::{id, nil},
18 foundation::{NSAutoreleasePool, NSFastEnumeration, NSString},
19};
20use core_foundation::{
21 array::CFArray,
22 base::{CFType, TCFType, TCFTypeRef},
23 boolean::CFBoolean,
24 declare_TCFType,
25 dictionary::CFDictionary,
26 impl_CFTypeDescription, impl_TCFType,
27 string::CFString,
28};
29use objc::{class, msg_send, rc::autoreleasepool, sel, sel_impl};
30
31use crate::{
32 util::{ax_call, ax_call_void},
33 AXAttribute, Error,
34};
35
36declare_TCFType!(AXUIElement, AXUIElementRef);
37impl_TCFType!(AXUIElement, AXUIElementRef, AXUIElementGetTypeID);
38impl_CFTypeDescription!(AXUIElement);
39
40unsafe impl Send for AXUIElement {}
41unsafe impl Sync for AXUIElement {}
42
43impl AXUIElement {
44 pub fn system_wide() -> Self {
45 unsafe { Self::wrap_under_create_rule(AXUIElementCreateSystemWide()) }
46 }
47
48 pub fn application(pid: pid_t) -> Self {
49 unsafe { Self::wrap_under_create_rule(AXUIElementCreateApplication(pid)) }
50 }
51
52 pub fn application_with_bundle(bundle_id: &str) -> Result<Self, Error> {
53 unsafe {
54 autoreleasepool(|| {
55 let bundle_id_str = NSString::alloc(nil).init_str(bundle_id).autorelease();
56 let apps: id = msg_send![
57 class![NSRunningApplication],
58 runningApplicationsWithBundleIdentifier: bundle_id_str
59 ];
60
61 if let Some(app) = apps.iter().next() {
62 let pid: pid_t = msg_send![app, processIdentifier];
63
64 Ok(Self::wrap_under_create_rule(AXUIElementCreateApplication(
65 pid,
66 )))
67 } else {
68 Err(Error::NotFound)
69 }
70 })
71 }
72 }
73
74 pub fn application_with_bundle_timeout(
75 bundle_id: &str,
76 timeout: Duration,
77 ) -> Result<Self, Error> {
78 let deadline = Instant::now() + timeout;
79
80 loop {
81 match Self::application_with_bundle(bundle_id) {
82 Ok(result) => return Ok(result),
83 Err(e) => {
84 let now = Instant::now();
85
86 if now >= deadline {
87 return Err(e);
88 } else {
89 let time_left = deadline.saturating_duration_since(now);
90 thread::sleep(std::cmp::min(time_left, Duration::from_millis(250)));
91 }
92 }
93 }
94 }
95 }
96
97 pub fn pid(&self) -> Result<pid_t, Error> {
98 unsafe { Ok(ax_call(|x| AXUIElementGetPid(self.0, x)).map_err(Error::Ax)?) }
99 }
100
101 pub fn attribute_names(&self) -> Result<CFArray<CFString>, Error> {
102 unsafe {
103 Ok(CFArray::wrap_under_create_rule(
104 ax_call(|x| AXUIElementCopyAttributeNames(self.0, x)).map_err(Error::Ax)?,
105 ))
106 }
107 }
108
109 pub fn parameterized_attribute_names(&self) -> Result<CFArray<CFString>, Error> {
110 unsafe {
111 Ok(CFArray::wrap_under_create_rule(
112 ax_call(|x| AXUIElementCopyParameterizedAttributeNames(self.0, x))
113 .map_err(Error::Ax)?,
114 ))
115 }
116 }
117
118 pub fn attribute<T: TCFType>(&self, attribute: &AXAttribute<T>) -> Result<T, Error> {
119 let res = unsafe {
120 Ok(T::wrap_under_create_rule(T::Ref::from_void_ptr(
121 ax_call(|x| {
122 AXUIElementCopyAttributeValue(
123 self.0,
124 attribute.as_CFString().as_concrete_TypeRef(),
125 x,
126 )
127 })
128 .map_err(Error::Ax)?,
129 )))
130 };
131 if let Ok(val) = &res {
132 if T::type_id() != CFType::type_id() && !val.instance_of::<T>() {
133 return Err(Error::UnexpectedType {
134 expected: T::type_id(),
135 received: val.type_of(),
136 });
137 }
138 }
139 res
140 }
141
142 pub fn set_attribute<T: TCFType>(
143 &self,
144 attribute: &AXAttribute<T>,
145 value: impl Into<T>,
146 ) -> Result<(), Error> {
147 let value = value.into();
148
149 unsafe {
150 Ok(ax_call_void(|| {
151 AXUIElementSetAttributeValue(
152 self.0,
153 attribute.as_CFString().as_concrete_TypeRef(),
154 value.as_CFTypeRef(),
155 )
156 })
157 .map_err(Error::Ax)?)
158 }
159 }
160
161 pub fn is_settable<T: TCFType>(&self, attribute: &AXAttribute<T>) -> Result<bool, Error> {
162 let settable: c_uchar = unsafe {
163 ax_call(|x| {
164 AXUIElementIsAttributeSettable(
165 self.0,
166 attribute.as_CFString().as_concrete_TypeRef(),
167 x,
168 )
169 })
170 .map_err(Error::Ax)?
171 };
172 Ok(settable != 0)
173 }
174
175 pub fn parameterized_attribute<T: TCFType, U: TCFType>(
176 &self,
177 attribute: &AXAttribute<T>,
178 parameter: &U,
179 ) -> Result<T, Error> {
180 unsafe {
181 Ok(T::wrap_under_create_rule(T::Ref::from_void_ptr(
182 ax_call(|x| {
183 AXUIElementCopyParameterizedAttributeValue(
184 self.0,
185 attribute.as_CFString().as_concrete_TypeRef(),
186 parameter.as_CFTypeRef(),
187 x,
188 )
189 })
190 .map_err(Error::Ax)?,
191 )))
192 }
193 }
194
195 pub fn action_names(&self) -> Result<CFArray<CFString>, Error> {
196 unsafe {
197 Ok(CFArray::wrap_under_create_rule(
198 ax_call(|x| AXUIElementCopyActionNames(self.0, x)).map_err(Error::Ax)?,
199 ))
200 }
201 }
202
203 pub fn perform_action(&self, name: &CFString) -> Result<(), Error> {
204 unsafe {
205 Ok(
206 ax_call_void(|| AXUIElementPerformAction(self.0, name.as_concrete_TypeRef()))
207 .map_err(Error::Ax)?,
208 )
209 }
210 }
211
212 pub fn set_messaging_timeout(&self, timeout: f32) -> Result<(), Error> {
213 unsafe {
214 Ok(
215 ax_call_void(|| AXUIElementSetMessagingTimeout(self.0, timeout))
216 .map_err(Error::Ax)?,
217 )
218 }
219 }
220
221 pub fn application_is_trusted() -> bool {
223 unsafe {
224 return AXIsProcessTrusted();
225 }
226 }
227
228 pub fn application_is_trusted_with_prompt() -> bool {
231 unsafe {
232 let option_prompt = CFString::wrap_under_get_rule(kAXTrustedCheckOptionPrompt);
233 let dict: CFDictionary<CFString, CFBoolean> =
234 CFDictionary::from_CFType_pairs(&[(option_prompt, CFBoolean::true_value())]);
235 return AXIsProcessTrustedWithOptions(dict.as_concrete_TypeRef());
236 }
237 }
238}
239
240impl Hash for AXUIElement {
241 fn hash<H: Hasher>(&self, state: &mut H) {
242 self.0.hash(state);
243 }
244}