nightlight 1.0.0

A CLI for configuring 'Night Shift' on macOS 🌕🌖🌗🌘🌑
Documentation
use crate::os::BlueLightStatus;
use objc::rc::StrongPtr;
use objc::runtime::{Object, BOOL, YES};
use objc::{class, msg_send, sel, sel_impl};
use std::mem::MaybeUninit;
use std::os::raw::{c_float, c_int};

pub struct Client {
    inner: StrongPtr,
}

impl Client {
    pub fn new() -> Client {
        Client {
            inner: Client::client(),
        }
    }

    pub fn set_enabled(&self, enabled: bool) -> Result<(), String> {
        let enabled = Box::new(enabled as BOOL);
        let result: BOOL = unsafe { msg_send![*self.inner, setEnabled: *enabled] };
        if result == YES {
            Ok(())
        } else {
            Err(format!("Failed to set enabled to {}", enabled).to_string())
        }
    }

    pub fn set_mode(&self, mode: c_int) -> Result<(), String> {
        let mode = Box::new(mode);
        let result: BOOL = unsafe { msg_send![*self.inner, setMode: *mode] };

        if result == YES {
            Ok(())
        } else {
            Err("Failed to set schedule".to_string())
        }
    }

    pub fn set_schedule(&self, from: (u8, u8), to: (u8, u8)) -> Result<(), String> {
        let ptr = Box::new(BlueLightStatus::sched_ptr(from, to));
        let result: BOOL = unsafe { msg_send![*self.inner, setSchedule: &*ptr] };

        if result == YES {
            Ok(())
        } else {
            Err("Failed to set schedule".to_string())
        }
    }

    pub fn set_strength(&self, strength: c_float) -> Result<(), String> {
        let strength = Box::new(strength);
        let result: BOOL = unsafe { msg_send![*self.inner, setStrength:*strength commit:YES] };

        if result == YES {
            Ok(())
        } else {
            Err("Failed to set color temperature".to_string())
        }
    }

    pub fn get_strength(&self) -> Result<i32, String> {
        let mut ptr: MaybeUninit<c_float> = MaybeUninit::uninit();
        let result: BOOL = unsafe { msg_send![*self.inner, getStrength: &mut ptr] };

        let value = unsafe { ptr.assume_init() };
        if result == YES && value >= 0.0 && value <= 1.0 {
            Ok((value * 100.0) as i32)
        } else {
            Err("Failed to get color temperature".to_string())
        }
    }

    pub fn get_enabled(&self) -> Result<bool, String> {
        Ok(self.status()?.enabled())
    }

    pub fn get_mode(&self) -> Result<i32, String> {
        Ok(self.status()?.mode())
    }

    pub fn get_schedule(&self) -> Result<((u8, u8), (u8, u8)), String> {
        Ok((self.status()?.from_time(), self.status()?.to_time()))
    }

    fn status(&self) -> Result<BlueLightStatus, String> {
        let mut ptr = BlueLightStatus::c_ptr();
        let result: BOOL = unsafe { msg_send![*self.inner, getBlueLightStatus: &mut ptr] };
        if result == YES {
            Ok(BlueLightStatus::new(unsafe { ptr.assume_init() }))
        } else {
            Err("Failed to get status".to_string())
        }
    }

    fn client() -> StrongPtr {
        let client_class = class!(CBBlueLightClient);
        unsafe {
            let obj: *mut Object = msg_send![client_class, alloc];
            let obj: *mut Object = msg_send![obj, init];
            StrongPtr::new(obj)
        }
    }
}