mac-cli 0.1.0

A simple CLI tool to control your Mac (volume, brightness, Bluetooth, Apple Music, weather)
//! Brightness control for macOS displays using the DisplayServices framework.
//!
//! This module provides an interface to get and set screen brightness on macOS
//! by accessing the private DisplayServices framework.

use core_graphics::display::{CGDirectDisplayID, CGGetActiveDisplayList};
use std::ffi::CString;
use std::os::raw::{c_char, c_float, c_int, c_void};

const RTLD_LAZY: c_int = 0x1;
const RTLD_DEFAULT: *mut c_void = -2isize as *mut c_void;

unsafe extern "C" {
    fn dlopen(filename: *const c_char, flag: c_int) -> *mut c_void;
    fn dlsym(handle: *mut c_void, symbol: *const c_char) -> *mut c_void;
    fn dlclose(handle: *mut c_void) -> c_int;
}

type DisplayServicesGetBrightnessFn =
    unsafe extern "C" fn(display: CGDirectDisplayID, brightness: *mut c_float) -> c_int;

type DisplayServicesSetBrightnessFn =
    unsafe extern "C" fn(display: CGDirectDisplayID, brightness: c_float) -> c_int;

/// Controller for managing display brightness on macOS.
///
/// Uses the DisplayServices framework to control the brightness of the primary display.
pub struct BrightnessController {
    display_id: CGDirectDisplayID,
    handle: *mut c_void,
    get_brightness_fn: DisplayServicesGetBrightnessFn,
    set_brightness_fn: DisplayServicesSetBrightnessFn,
}

impl BrightnessController {
    /// Creates a new brightness controller for the primary display.
    ///
    /// # Errors
    ///
    /// Returns an error if no active displays are found or if the DisplayServices
    /// framework functions are not available.
    pub fn new() -> Result<Self, String> {
        let mut display_count: u32 = 0;
        let mut displays: [CGDirectDisplayID; 16] = [0; 16];

        unsafe {
            let result =
                CGGetActiveDisplayList(16, displays.as_mut_ptr(), &mut display_count);

            if result != 0 {
                return Err("Failed to get active displays".to_string());
            }

            if display_count == 0 {
                return Err("No active displays found".to_string());
            }

            let framework_paths = vec![
                "/System/Library/PrivateFrameworks/DisplayServices.framework/DisplayServices",
                "/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight",
            ];

            let mut handle = std::ptr::null_mut();
            for path in &framework_paths {
                let framework_path = CString::new(*path).unwrap();
                handle = dlopen(framework_path.as_ptr(), RTLD_LAZY);
                if !handle.is_null() {
                    break;
                }
            }

            let search_handle = if handle.is_null() {
                RTLD_DEFAULT
            } else {
                handle
            };

            let get_brightness_name = CString::new("DisplayServicesGetBrightness").unwrap();
            let set_brightness_name = CString::new("DisplayServicesSetBrightness").unwrap();

            let get_fn_ptr = dlsym(search_handle, get_brightness_name.as_ptr());
            let set_fn_ptr = dlsym(search_handle, set_brightness_name.as_ptr());

            if get_fn_ptr.is_null() || set_fn_ptr.is_null() {
                if !handle.is_null() {
                    dlclose(handle);
                }
                return Err("DisplayServices functions not available on this system.".to_string());
            }

            let get_brightness_fn: DisplayServicesGetBrightnessFn =
                std::mem::transmute(get_fn_ptr);
            let set_brightness_fn: DisplayServicesSetBrightnessFn =
                std::mem::transmute(set_fn_ptr);

            Ok(BrightnessController {
                display_id: displays[0],
                handle,
                get_brightness_fn,
                set_brightness_fn,
            })
        }
    }

    /// Gets the current brightness level.
    ///
    /// # Returns
    ///
    /// Returns a value between 0.0 (minimum) and 1.0 (maximum).
    pub fn get(&self) -> Result<f32, String> {
        let mut brightness: c_float = 0.0;

        unsafe {
            let result = (self.get_brightness_fn)(self.display_id, &mut brightness);
            if result != 0 {
                return Err(format!("Failed to get brightness: error code {}", result));
            }
        }

        Ok(brightness)
    }

    /// Sets the brightness level.
    ///
    /// # Arguments
    ///
    /// * `brightness` - A value between 0.0 (minimum) and 1.0 (maximum).
    ///
    /// # Errors
    ///
    /// Returns an error if the brightness value is out of range or if setting fails.
    pub fn set(&self, brightness: f32) -> Result<(), String> {
        if !(0.0..=1.0).contains(&brightness) {
            return Err("Brightness must be between 0.0 and 1.0".to_string());
        }

        unsafe {
            let result = (self.set_brightness_fn)(self.display_id, brightness);
            if result != 0 {
                return Err(format!("Failed to set brightness: error code {}", result));
            }
        }

        Ok(())
    }
}

impl Drop for BrightnessController {
    fn drop(&mut self) {
        unsafe {
            if !self.handle.is_null() {
                dlclose(self.handle);
            }
        }
    }
}