#![cfg(all(target_os = "macos", feature = "macos"))]
use std::sync::atomic::{AtomicBool, Ordering};
use objc2::rc::Retained;
use objc2::runtime::{AnyObject, NSObject};
use objc2::{define_class, sel};
use objc2_foundation::{NSNotification, NSNotificationCenter, NSString};
const SYSTEM_COLORS_DID_CHANGE: &str = "NSSystemColorsDidChangeNotification";
static INSTALLED: AtomicBool = AtomicBool::new(false);
define_class!(
#[unsafe(super(NSObject))]
#[name = "RustWidgetsObjc2SystemColorObserver"]
struct SystemColorObserver;
impl SystemColorObserver {
#[unsafe(method(systemColorsDidChange:))]
fn system_colors_did_change(&self, _notification: &NSNotification) {
let outcome = std::panic::catch_unwind(|| {
#[cfg(device_profile)]
{
let changed = crate::theme::global_theme_manager().refresh_system_accent();
if changed {
log::debug!(
"[macos-objc2] system colours changed; the theme accent was re-derived"
);
}
}
});
if outcome.is_err() {
log::error!(
"[macos-objc2] the system colour observer panicked while refreshing the accent"
);
}
}
}
);
impl SystemColorObserver {
objc2::extern_methods!(
#[unsafe(method(new))]
fn new() -> Retained<Self>;
);
}
pub(crate) fn install_system_color_observer() -> bool {
if INSTALLED.load(Ordering::Relaxed) {
return true;
}
if objc2::MainThreadMarker::new().is_none() {
return false;
}
let name = NSString::from_str(SYSTEM_COLORS_DID_CHANGE);
let observer = SystemColorObserver::new();
let center = NSNotificationCenter::defaultCenter();
unsafe {
center.addObserver_selector_name_object(
&observer,
sel!(systemColorsDidChange:),
Some(&name),
None::<&AnyObject>,
);
}
core::mem::forget(observer);
INSTALLED.store(true, Ordering::Relaxed);
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn installation_is_idempotent() {
let _ = install_system_color_observer();
let first = INSTALLED.load(Ordering::Relaxed);
let _ = install_system_color_observer();
assert_eq!(INSTALLED.load(Ordering::Relaxed), first);
}
}