iohidmanager 0.10.4

Safe Rust bindings for Apple's IOKit HID — enumerate, inspect, and subscribe to HID devices on macOS
Documentation
use core::ptr;

#[allow(clippy::wildcard_imports)]
use super::*;
use crate::{bridge, ffi_impl as ffi};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// Wraps `IOHIDTransactionDirectionType`.
pub enum HidTransactionDirection {
    /// Mirrors `kIOHIDTransactionDirectionTypeInput`.
    Input,
    /// Mirrors `kIOHIDTransactionDirectionTypeOutput`.
    Output,
}

impl HidTransactionDirection {
    #[must_use]
    /// Mirrors `IOHIDTransactionDirectionType`.
    pub const fn as_raw(self) -> ffi::IOHIDTransactionDirectionType {
        match self {
            Self::Input => ffi::kIOHIDTransactionDirectionTypeInput,
            Self::Output => ffi::kIOHIDTransactionDirectionTypeOutput,
        }
    }

    #[must_use]
    /// Mirrors `IOHIDTransactionDirectionType`.
    pub const fn from_raw(raw: ffi::IOHIDTransactionDirectionType) -> Self {
        match raw {
            ffi::kIOHIDTransactionDirectionTypeOutput => Self::Output,
            _ => Self::Input,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// Wraps the option bits consumed by `IOHIDTransactionCreate`.
pub struct HidTransactionOptions(u32);

impl HidTransactionOptions {
    /// Mirrors `kIOHIDTransactionOptionsNone`.
    pub const NONE: Self = Self(ffi::kIOHIDTransactionOptionsNone);
    /// Mirrors `kIOHIDTransactionOptionsWeakDevice`.
    pub const WEAK_DEVICE: Self = Self(ffi::kIOHIDTransactionOptionsWeakDevice);

    #[must_use]
    /// Returns the raw option bits passed to `IOHIDTransactionCreate`.
    pub const fn bits(self) -> u32 {
        self.0
    }
}

/// Owns an `IOHIDTransactionRef` returned by `IOHIDTransactionCreate`.
pub struct HidTransaction {
    raw: ffi::IOHIDTransactionRef,
}

unsafe impl Send for HidTransaction {}
unsafe impl Sync for HidTransaction {}

impl Clone for HidTransaction {
    fn clone(&self) -> Self {
        unsafe { ffi::CFRetain(self.raw.cast_const()) };
        Self { raw: self.raw }
    }
}

impl Drop for HidTransaction {
    fn drop(&mut self) {
        if !self.raw.is_null() {
            unsafe { ffi::CFRelease(self.raw.cast_const()) };
            self.raw = ptr::null_mut();
        }
    }
}

#[allow(clippy::missing_errors_doc)]
impl HidTransaction {
    #[must_use]
    /// Wraps `IOHIDTransactionGetTypeID`.
    pub fn type_id() -> ffi::CFTypeID {
        unsafe { bridge::iohidmanager_swift_transaction_type_id() }
    }

    /// Wraps `IOHIDTransactionCreate` with `kIOHIDTransactionOptionsNone`.
    pub fn new(device: &HidDevice, direction: HidTransactionDirection) -> Result<Self, HidError> {
        Self::with_options(device, direction, HidTransactionOptions::NONE)
    }

    /// Wraps `IOHIDTransactionCreate`.
    pub fn with_options(
        device: &HidDevice,
        direction: HidTransactionDirection,
        options: HidTransactionOptions,
    ) -> Result<Self, HidError> {
        let raw = unsafe {
            ffi::IOHIDTransactionCreate(
                ffi::kCFAllocatorDefault,
                device.raw,
                direction.as_raw(),
                options.bits(),
            )
        };
        if raw.is_null() {
            Err(HidError::OperationFailed("IOHIDTransactionCreate"))
        } else {
            Ok(Self { raw })
        }
    }

    #[must_use]
    /// Wraps `IOHIDTransactionGetDevice`.
    pub fn device(&self) -> Option<HidDevice> {
        let device = unsafe { ffi::IOHIDTransactionGetDevice(self.raw) };
        if device.is_null() {
            None
        } else {
            unsafe { ffi::CFRetain(device) };
            Some(HidDevice { raw: device })
        }
    }

    #[must_use]
    /// Wraps `IOHIDTransactionGetDirection`.
    pub fn direction(&self) -> HidTransactionDirection {
        HidTransactionDirection::from_raw(unsafe { ffi::IOHIDTransactionGetDirection(self.raw) })
    }

    /// Wraps `IOHIDTransactionSetDirection`.
    pub fn set_direction(&self, direction: HidTransactionDirection) {
        unsafe { ffi::IOHIDTransactionSetDirection(self.raw, direction.as_raw()) };
    }

    /// Wraps `IOHIDTransactionAddElement`.
    pub fn add_element(&self, element: &HidElement) {
        unsafe { ffi::IOHIDTransactionAddElement(self.raw, element.raw) };
    }

    /// Wraps `IOHIDTransactionRemoveElement`.
    pub fn remove_element(&self, element: &HidElement) {
        unsafe { ffi::IOHIDTransactionRemoveElement(self.raw, element.raw) };
    }

    #[must_use]
    /// Wraps `IOHIDTransactionContainsElement`.
    pub fn contains_element(&self, element: &HidElement) -> bool {
        unsafe { ffi::IOHIDTransactionContainsElement(self.raw, element.raw) }
    }

    /// Wraps `IOHIDTransactionScheduleWithRunLoop` for the current run loop.
    pub fn schedule_current_run_loop(&self) {
        let run_loop = unsafe { ffi::CFRunLoopGetCurrent() };
        unsafe {
            ffi::IOHIDTransactionScheduleWithRunLoop(
                self.raw,
                run_loop,
                ffi::kCFRunLoopDefaultMode,
            );
        }
    }

    /// Wraps `IOHIDTransactionUnscheduleFromRunLoop` for the current run loop.
    pub fn unschedule_current_run_loop(&self) {
        let run_loop = unsafe { ffi::CFRunLoopGetCurrent() };
        unsafe {
            ffi::IOHIDTransactionUnscheduleFromRunLoop(
                self.raw,
                run_loop,
                ffi::kCFRunLoopDefaultMode,
            );
        }
    }

    /// Wraps `IOHIDTransactionSetValue`.
    pub fn set_value(&self, element: &HidElement, value: &HidValue, options: ffi::IOOptionBits) {
        unsafe { ffi::IOHIDTransactionSetValue(self.raw, element.raw, value.raw, options) };
    }

    #[must_use]
    /// Wraps `IOHIDTransactionGetValue`.
    pub fn get_value(&self, element: &HidElement, options: ffi::IOOptionBits) -> Option<HidValue> {
        clone_value_ref(unsafe { ffi::IOHIDTransactionGetValue(self.raw, element.raw, options) })
    }

    /// Wraps `IOHIDTransactionCommit`.
    pub fn commit(&self) -> Result<(), HidError> {
        let status = unsafe { ffi::IOHIDTransactionCommit(self.raw) };
        if status == ffi::kIOReturnSuccess {
            Ok(())
        } else {
            Err(HidError::IoReturn("IOHIDTransactionCommit", status))
        }
    }

    /// Wraps `IOHIDTransactionCommitWithCallback`.
    pub fn commit_with_timeout(&self, timeout_ms: f64) -> Result<(), HidError> {
        let status = unsafe {
            ffi::IOHIDTransactionCommitWithCallback(self.raw, timeout_ms, None, ptr::null_mut())
        };
        if status == ffi::kIOReturnSuccess {
            Ok(())
        } else {
            Err(HidError::IoReturn(
                "IOHIDTransactionCommitWithCallback",
                status,
            ))
        }
    }

    /// Wraps `IOHIDTransactionClear`.
    pub fn clear(&self) {
        unsafe { ffi::IOHIDTransactionClear(self.raw) };
    }

    #[must_use]
    /// Mirrors `IOHIDTransactionRef`.
    pub const fn as_ptr(&self) -> ffi::IOHIDTransactionRef {
        self.raw
    }
}