use core::ffi::{c_char, c_void};
use core::ptr;
use std::ffi::CString;
use crate::error::AXError;
use crate::ffi;
pub struct AXElement {
raw: ffi::AXUIElementRef,
}
unsafe impl Send for AXElement {}
unsafe impl Sync for AXElement {}
impl Drop for AXElement {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe { ffi::CFRelease(self.raw.cast_const()) };
self.raw = ptr::null_mut();
}
}
}
impl AXElement {
#[must_use]
pub fn from_pid(pid: i32) -> Option<Self> {
let raw = unsafe { ffi::AXUIElementCreateApplication(pid) };
if raw.is_null() {
None
} else {
Some(Self { raw })
}
}
#[must_use]
pub fn system_wide() -> Option<Self> {
let raw = unsafe { ffi::AXUIElementCreateSystemWide() };
if raw.is_null() {
None
} else {
Some(Self { raw })
}
}
pub fn set_timeout(&self, timeout_seconds: f32) -> Result<(), AXError> {
let s = unsafe { ffi::AXUIElementSetMessagingTimeout(self.raw, timeout_seconds) };
if s == ffi::kAXErrorSuccess {
Ok(())
} else {
Err(AXError::from_status(s, "set_timeout"))
}
}
pub fn pid(&self) -> Result<i32, AXError> {
let mut pid: ffi::pid_t = 0;
let s = unsafe { ffi::AXUIElementGetPid(self.raw, &mut pid) };
if s == ffi::kAXErrorSuccess {
Ok(pid)
} else {
Err(AXError::from_status(s, "pid"))
}
}
pub fn attribute_names(&self) -> Result<Vec<String>, AXError> {
let mut arr: ffi::CFArrayRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyAttributeNames(self.raw, &mut arr) };
if s != ffi::kAXErrorSuccess {
return Err(AXError::from_status(s, "attribute_names"));
}
if arr.is_null() {
return Ok(Vec::new());
}
let count = unsafe { ffi::CFArrayGetCount(arr) };
let mut out = Vec::with_capacity(usize::try_from(count).unwrap_or(0));
for i in 0..count {
let v = unsafe { ffi::CFArrayGetValueAtIndex(arr, i) };
if let Some(s) = cf_string_to_string(v) {
out.push(s);
}
}
unsafe { ffi::CFRelease(arr) };
Ok(out)
}
pub fn string_attribute(&self, name: &str) -> Result<Option<String>, AXError> {
let cf_name = make_cfstring(name)?;
let mut value: ffi::CFTypeRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyAttributeValue(self.raw, cf_name, &mut value) };
unsafe { ffi::CFRelease(cf_name) };
if s != ffi::kAXErrorSuccess {
if s == ffi::kAXErrorNoValue {
return Ok(None);
}
return Err(AXError::from_status(s, name));
}
let out = cf_string_to_string(value);
unsafe { ffi::CFRelease(value) };
Ok(out)
}
pub fn element_attribute(&self, name: &str) -> Result<Option<Self>, AXError> {
let cf_name = make_cfstring(name)?;
let mut value: ffi::CFTypeRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyAttributeValue(self.raw, cf_name, &mut value) };
unsafe { ffi::CFRelease(cf_name) };
if s != ffi::kAXErrorSuccess {
if s == ffi::kAXErrorNoValue {
return Ok(None);
}
return Err(AXError::from_status(s, name));
}
if value.is_null() {
return Ok(None);
}
Ok(Some(Self { raw: value.cast_mut() }))
}
pub fn action_names(&self) -> Result<Vec<String>, AXError> {
let mut arr: ffi::CFArrayRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyActionNames(self.raw, &mut arr) };
if s != ffi::kAXErrorSuccess {
return Err(AXError::from_status(s, "action_names"));
}
if arr.is_null() {
return Ok(Vec::new());
}
let count = unsafe { ffi::CFArrayGetCount(arr) };
let mut out = Vec::with_capacity(usize::try_from(count).unwrap_or(0));
for i in 0..count {
let v = unsafe { ffi::CFArrayGetValueAtIndex(arr, i) };
if let Some(s) = cf_string_to_string(v) {
out.push(s);
}
}
unsafe { ffi::CFRelease(arr) };
Ok(out)
}
pub fn perform_action(&self, action: &str) -> Result<(), AXError> {
let cf_action = make_cfstring(action)?;
let s = unsafe { ffi::AXUIElementPerformAction(self.raw, cf_action) };
unsafe { ffi::CFRelease(cf_action) };
if s == ffi::kAXErrorSuccess {
Ok(())
} else {
Err(AXError::from_status(s, action))
}
}
}
#[must_use]
pub fn is_process_trusted() -> bool {
unsafe { ffi::AXIsProcessTrusted() }
}
#[must_use]
pub fn api_enabled() -> bool {
unsafe { ffi::AXAPIEnabled() }
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct AXPoint {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct AXSize {
pub width: f64,
pub height: f64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct AXRect {
pub origin: AXPoint,
pub size: AXSize,
}
impl AXElement {
pub fn point_attribute(&self, name: &str) -> Result<Option<AXPoint>, AXError> {
let cf_name = make_cfstring(name)?;
let mut value: ffi::CFTypeRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyAttributeValue(self.raw, cf_name, &mut value) };
unsafe { ffi::CFRelease(cf_name) };
if s != ffi::kAXErrorSuccess {
if s == ffi::kAXErrorNoValue {
return Ok(None);
}
return Err(AXError::from_status(s, name));
}
if value.is_null() {
return Ok(None);
}
if unsafe { ffi::AXValueGetType(value) } != ffi::kAXValueTypeCGPoint {
unsafe { ffi::CFRelease(value) };
return Ok(None);
}
let mut p = AXPoint::default();
let ok = unsafe {
ffi::AXValueGetValue(
value,
ffi::kAXValueTypeCGPoint,
core::ptr::from_mut(&mut p).cast(),
)
};
unsafe { ffi::CFRelease(value) };
if ok { Ok(Some(p)) } else { Ok(None) }
}
pub fn size_attribute(&self, name: &str) -> Result<Option<AXSize>, AXError> {
let cf_name = make_cfstring(name)?;
let mut value: ffi::CFTypeRef = ptr::null();
let s = unsafe { ffi::AXUIElementCopyAttributeValue(self.raw, cf_name, &mut value) };
unsafe { ffi::CFRelease(cf_name) };
if s != ffi::kAXErrorSuccess {
if s == ffi::kAXErrorNoValue {
return Ok(None);
}
return Err(AXError::from_status(s, name));
}
if value.is_null() {
return Ok(None);
}
if unsafe { ffi::AXValueGetType(value) } != ffi::kAXValueTypeCGSize {
unsafe { ffi::CFRelease(value) };
return Ok(None);
}
let mut sz = AXSize::default();
let ok = unsafe {
ffi::AXValueGetValue(
value,
ffi::kAXValueTypeCGSize,
core::ptr::from_mut(&mut sz).cast(),
)
};
unsafe { ffi::CFRelease(value) };
if ok { Ok(Some(sz)) } else { Ok(None) }
}
pub fn set_point_attribute(&self, name: &str, value: AXPoint) -> Result<(), AXError> {
let cf_name = make_cfstring(name)?;
let v = unsafe {
ffi::AXValueCreate(
ffi::kAXValueTypeCGPoint,
core::ptr::from_ref(&value).cast(),
)
};
if v.is_null() {
unsafe { ffi::CFRelease(cf_name) };
return Err(AXError::Failure);
}
let s = unsafe { ffi::AXUIElementSetAttributeValue(self.raw, cf_name, v) };
unsafe {
ffi::CFRelease(v);
ffi::CFRelease(cf_name);
}
if s == ffi::kAXErrorSuccess {
Ok(())
} else {
Err(AXError::from_status(s, name))
}
}
pub fn set_size_attribute(&self, name: &str, value: AXSize) -> Result<(), AXError> {
let cf_name = make_cfstring(name)?;
let v = unsafe {
ffi::AXValueCreate(
ffi::kAXValueTypeCGSize,
core::ptr::from_ref(&value).cast(),
)
};
if v.is_null() {
unsafe { ffi::CFRelease(cf_name) };
return Err(AXError::Failure);
}
let s = unsafe { ffi::AXUIElementSetAttributeValue(self.raw, cf_name, v) };
unsafe {
ffi::CFRelease(v);
ffi::CFRelease(cf_name);
}
if s == ffi::kAXErrorSuccess {
Ok(())
} else {
Err(AXError::from_status(s, name))
}
}
}
fn make_cfstring(s: &str) -> Result<ffi::CFStringRef, AXError> {
let c =
CString::new(s).map_err(|e| AXError::IllegalArgument(format!("CString: {e}")))?;
let cf = unsafe {
ffi::CFStringCreateWithCString(
ffi::kCFAllocatorDefault,
c.as_ptr(),
ffi::kCFStringEncodingUTF8,
)
};
if cf.is_null() {
return Err(AXError::IllegalArgument(format!(
"CFStringCreateWithCString failed for {s:?}"
)));
}
Ok(cf)
}
fn cf_string_to_string(v: *const c_void) -> Option<String> {
if v.is_null() {
return None;
}
if unsafe { ffi::CFGetTypeID(v) } != unsafe { ffi::CFStringGetTypeID() } {
return None;
}
let s = v as ffi::CFStringRef;
let len = unsafe { ffi::CFStringGetLength(s) };
let cap = (len * 4) + 1;
let mut buf = vec![0u8; usize::try_from(cap).unwrap_or(0)];
let ok = unsafe {
ffi::CFStringGetCString(
s,
buf.as_mut_ptr().cast::<c_char>(),
cap,
ffi::kCFStringEncodingUTF8,
)
};
if !ok {
return None;
}
if let Some(end) = buf.iter().position(|&b| b == 0) {
buf.truncate(end);
}
String::from_utf8(buf).ok()
}