user-idle 0.6.0

Get a users idle time.
Documentation
use std::{io, mem::size_of, ptr::null_mut, time::Duration};

use mach::{
    kern_return::KERN_SUCCESS,
    port::{mach_port_t, MACH_PORT_NULL},
};
use CoreFoundation_sys::{
    kCFAllocatorDefault, kCFNumberSInt64Type, kCFStringEncodingUTF8,
    CFDataGetBytes, CFDataGetTypeID, CFDictionaryGetValueIfPresent,
    CFGetTypeID, CFNumberGetTypeID, CFNumberGetValue, CFRange, CFRelease,
    CFStringCreateWithCString, CFTypeRef,
};
use IOKit_sys::{
    IOIteratorNext, IOMasterPort, IOObjectRelease,
    IORegistryEntryCreateCFProperties, IOServiceGetMatchingServices,
    IOServiceMatching,
};

use crate::error::Error;

pub fn get_idle_time() -> Result<Duration, Error> {
    let mut ns = 0u64;
    let mut port: mach_port_t = 0;
    let mut iter = 0;
    let mut value: CFTypeRef = null_mut();
    let mut properties = null_mut();
    let entry;

    unsafe {
        let port_result = IOMasterPort(MACH_PORT_NULL, &mut port as _);
        if port_result != KERN_SUCCESS {
            return Err(Error {
                cause: format!(
                    "Unable to open mach port: {}",
                    io::Error::last_os_error()
                ),
            });
        }

        let service_name = cstr::cstr!("IOHIDSystem");
        let service_result = IOServiceGetMatchingServices(
            port as _,
            IOServiceMatching(service_name.as_ptr() as _),
            &mut iter,
        );
        if service_result != KERN_SUCCESS {
            return Err(Error {
                cause: format!(
                    "Unable to lookup IOHIDSystem: {}",
                    io::Error::last_os_error()
                ),
            });
        }

        if iter > 0 {
            entry = IOIteratorNext(iter);
            if entry > 0 {
                let prop_res = IORegistryEntryCreateCFProperties(
                    entry,
                    &mut properties as _,
                    kCFAllocatorDefault,
                    0,
                );

                if prop_res == KERN_SUCCESS {
                    let prop_name = cstr::cstr!("HIDIdleTime");
                    let prop_name_cf = CFStringCreateWithCString(
                        kCFAllocatorDefault,
                        prop_name.as_ptr() as _,
                        kCFStringEncodingUTF8,
                    );
                    let present = CFDictionaryGetValueIfPresent(
                        properties,
                        prop_name_cf as _,
                        &mut value,
                    );
                    CFRelease(prop_name_cf.cast());

                    if present == 1 {
                        IOObjectRelease(iter);
                        IOObjectRelease(entry as _);
                        CFRelease(properties as _);
                        if CFGetTypeID(value) == CFDataGetTypeID() {
                            let mut buf = [0u8; size_of::<i64>()];
                            let range = CFRange {
                                location: buf.as_ptr() as _,
                                length: size_of::<i64>() as _,
                            };
                            CFDataGetBytes(value as _, range, buf.as_mut_ptr());
                            ns = i64::from_ne_bytes(buf) as u64;
                        } else if CFGetTypeID(value) == CFNumberGetTypeID() {
                            let mut buf = [0i64, 1];
                            CFNumberGetValue(
                                value as _,
                                kCFNumberSInt64Type,
                                buf.as_mut_ptr() as _,
                            );
                            ns = buf[0] as u64;
                        }
                    }
                }
            }
            IOObjectRelease(entry as _);
        }
        IOObjectRelease(iter);
    }

    Ok(Duration::from_nanos(ns))
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn no_panics() {
        assert!(get_idle_time().is_ok());
    }
}