axuielement 0.9.1

Safe Rust bindings for Apple's AXUIElement — drive other apps' UIs (read attributes, perform actions) on macOS
Documentation
//! Safe wrappers for `AXTextMarker` and `AXTextMarkerRange`.

use core::ffi::c_void;
use core::fmt;

use crate::bridge;

#[repr(transparent)]
/// Owned wrapper around an `ApplicationServices` `AXTextMarkerRef`.
pub struct AXTextMarker {
    raw: *mut c_void,
}

unsafe impl Send for AXTextMarker {}
unsafe impl Sync for AXTextMarker {}

impl Clone for AXTextMarker {
    fn clone(&self) -> Self {
        // SAFETY: FFI call with valid arguments
        let raw = unsafe { bridge::ax_text_marker::ax_text_marker_retain(self.raw) };
        // SAFETY: pointer is guaranteed valid from the bridge
        unsafe { Self::from_raw(raw) }
    }
}

impl Drop for AXTextMarker {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            // SAFETY: FFI boundary with properly validated inputs
            unsafe { bridge::ax_text_marker::ax_text_marker_release(self.raw) };
            self.raw = core::ptr::null_mut();
        }
    }
}

impl fmt::Debug for AXTextMarker {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AXTextMarker")
            .field("len", &self.len())
            .finish()
    }
}

impl AXTextMarker {
    #[must_use]
    /// Wraps `AXTextMarkerGetTypeID`.
    pub fn type_id() -> usize {
        // SAFETY: FFI boundary with properly validated inputs
        unsafe { bridge::ax_text_marker::ax_text_marker_get_type_id() }
    }

    #[must_use]
    /// Wraps `AXTextMarkerCreate`.
    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
        let raw =
            // SAFETY: FFI boundary with properly validated inputs
            unsafe { bridge::ax_text_marker::ax_text_marker_create(bytes.as_ptr(), bytes.len()) };
        if raw.is_null() {
            None
        } else {
            // SAFETY: pointer is guaranteed valid from the bridge
            Some(unsafe { Self::from_raw(raw) })
        }
    }

    #[must_use]
    /// Wraps `AXTextMarkerGetLength`.
    pub fn len(&self) -> usize {
        // SAFETY: FFI boundary with properly validated inputs
        unsafe { bridge::ax_text_marker::ax_text_marker_len(self.raw) }
    }

    #[must_use]
    /// Returns whether `AXTextMarkerGetLength` reports an empty marker.
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    #[must_use]
    /// Copies the bytes exposed by `AXTextMarkerGetBytePtr`.
    pub fn bytes(&self) -> Vec<u8> {
        let len = self.len();
        let mut bytes = vec![0_u8; len];
        if len == 0 {
            return bytes;
        }
        // SAFETY: FFI call with valid arguments
        let _ = unsafe {
            bridge::ax_text_marker::ax_text_marker_copy_bytes(
                self.raw,
                bytes.as_mut_ptr(),
                bytes.len(),
            )
        };
        bytes
    }

    pub(crate) unsafe fn from_raw(raw: *mut c_void) -> Self {
        Self { raw }
    }

    pub(crate) const fn as_ptr(&self) -> *mut c_void {
        self.raw
    }
}

#[repr(transparent)]
/// Owned wrapper around an `ApplicationServices` `AXTextMarkerRangeRef`.
pub struct AXTextMarkerRange {
    raw: *mut c_void,
}

unsafe impl Send for AXTextMarkerRange {}
unsafe impl Sync for AXTextMarkerRange {}

impl Clone for AXTextMarkerRange {
    fn clone(&self) -> Self {
        // SAFETY: FFI call with valid arguments
        let raw = unsafe { bridge::ax_text_marker::ax_text_marker_range_retain(self.raw) };
        // SAFETY: pointer is guaranteed valid from the bridge
        unsafe { Self::from_raw(raw) }
    }
}

impl Drop for AXTextMarkerRange {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            // SAFETY: FFI boundary with properly validated inputs
            unsafe { bridge::ax_text_marker::ax_text_marker_range_release(self.raw) };
            self.raw = core::ptr::null_mut();
        }
    }
}

impl fmt::Debug for AXTextMarkerRange {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("AXTextMarkerRange").finish()
    }
}

impl AXTextMarkerRange {
    #[must_use]
    /// Wraps `AXTextMarkerGetTypeID`.
    pub fn type_id() -> usize {
        // SAFETY: FFI boundary with properly validated inputs
        unsafe { bridge::ax_text_marker::ax_text_marker_range_get_type_id() }
    }

    #[must_use]
    /// Wraps `AXTextMarkerRangeCreate`.
    pub fn new(start: &AXTextMarker, end: &AXTextMarker) -> Option<Self> {
        // SAFETY: FFI call with valid arguments
        let raw = unsafe {
            bridge::ax_text_marker::ax_text_marker_range_create(start.as_ptr(), end.as_ptr())
        };
        if raw.is_null() {
            None
        } else {
            // SAFETY: pointer is guaranteed valid from the bridge
            Some(unsafe { Self::from_raw(raw) })
        }
    }

    #[must_use]
    /// Wraps `AXTextMarkerRangeCreateWithBytes`.
    pub fn from_bytes(start: &[u8], end: &[u8]) -> Option<Self> {
        // SAFETY: FFI call with valid arguments
        let raw = unsafe {
            bridge::ax_text_marker::ax_text_marker_range_create_with_bytes(
                start.as_ptr(),
                start.len(),
                end.as_ptr(),
                end.len(),
            )
        };
        if raw.is_null() {
            None
        } else {
            // SAFETY: pointer is guaranteed valid from the bridge
            Some(unsafe { Self::from_raw(raw) })
        }
    }

    #[must_use]
    /// Wraps `AXTextMarkerRangeCopyStartMarker`.
    pub fn start_marker(&self) -> AXTextMarker {
        let raw =
            // SAFETY: FFI boundary with properly validated inputs
            unsafe { bridge::ax_text_marker::ax_text_marker_range_copy_start_marker(self.raw) };
        // SAFETY: pointer is guaranteed valid from the bridge
        unsafe { AXTextMarker::from_raw(raw) }
    }

    #[must_use]
    /// Wraps `AXTextMarkerRangeCopyEndMarker`.
    pub fn end_marker(&self) -> AXTextMarker {
        // SAFETY: FFI call with valid arguments
        let raw = unsafe { bridge::ax_text_marker::ax_text_marker_range_copy_end_marker(self.raw) };
        // SAFETY: pointer is guaranteed valid from the bridge
        unsafe { AXTextMarker::from_raw(raw) }
    }

    pub(crate) unsafe fn from_raw(raw: *mut c_void) -> Self {
        Self { raw }
    }

    pub(crate) const fn as_ptr(&self) -> *mut c_void {
        self.raw
    }
}