grayscale 0.0.1

Enable or disable macOS grayscale display mode
//! Toggle macOS system-wide grayscale mode.
//!
//! This crate provides a simple API to control the system-wide grayscale
//! display filter on macOS. It works by calling `UAGrayscaleSetEnabled` and
//! `UAGrayscaleIsEnabled` from Apple's private `UniversalAccess.framework`,
//! loaded at runtime via `dlopen`. No Objective-C runtime or additional
//! dependencies are required.
//!
//! # Example
//!
//! ```no_run
//! // Turn grayscale on
//! grayscale::enable();
//! assert!(grayscale::is_enabled());
//!
//! // Turn it back off
//! grayscale::disable();
//! assert!(!grayscale::is_enabled());
//!
//! // Or toggle it
//! let new_state = grayscale::toggle();
//! ```
//!
//! # Platform support
//!
//! This crate only compiles on macOS. Attempting to build on other platforms
//! produces a compile-time error.

#[cfg(not(target_os = "macos"))]
compile_error!("grayscale only supports macOS");

use std::ffi::c_void;
use std::sync::Once;

const UA_PATH: &[u8] = b"/System/Library/PrivateFrameworks/UniversalAccess.framework/UniversalAccess\0";

type IsEnabledFn = unsafe extern "C" fn() -> bool;
type SetEnabledFn = unsafe extern "C" fn(bool);

static mut IS_ENABLED_FN: Option<IsEnabledFn> = None;
static mut SET_ENABLED_FN: Option<SetEnabledFn> = None;
static INIT: Once = Once::new();

unsafe extern "C" {
    fn dlopen(path: *const u8, mode: i32) -> *mut c_void;
    fn dlsym(handle: *mut c_void, symbol: *const u8) -> *mut c_void;
}

fn load() {
    INIT.call_once(|| unsafe {
        let handle = dlopen(UA_PATH.as_ptr(), 1 /* RTLD_LAZY */);
        assert!(!handle.is_null(), "failed to load UniversalAccess.framework");

        let is_sym = dlsym(handle, b"UAGrayscaleIsEnabled\0".as_ptr());
        let set_sym = dlsym(handle, b"UAGrayscaleSetEnabled\0".as_ptr());
        assert!(!is_sym.is_null() && !set_sym.is_null(), "failed to find UAGrayscale symbols");

        IS_ENABLED_FN = Some(std::mem::transmute(is_sym));
        SET_ENABLED_FN = Some(std::mem::transmute(set_sym));
    });
}

/// Returns `true` if grayscale mode is currently enabled.
pub fn is_enabled() -> bool {
    load();
    unsafe { (IS_ENABLED_FN.unwrap())() }
}

/// Enable grayscale mode.
pub fn enable() {
    load();
    unsafe { (SET_ENABLED_FN.unwrap())(true) }
}

/// Disable grayscale mode.
pub fn disable() {
    load();
    unsafe { (SET_ENABLED_FN.unwrap())(false) }
}

/// Toggle grayscale mode. Returns the new state.
pub fn toggle() -> bool {
    load();
    let new_state = unsafe { !(IS_ENABLED_FN.unwrap())() };
    unsafe { (SET_ENABLED_FN.unwrap())(new_state) }
    new_state
}