use crate::{DesktopEvent, EventRegistry};
use objc2::rc::Retained;
use objc2::{define_class, msg_send, sel, DefinedClass, MainThreadMarker, MainThreadOnly};
use objc2_app_kit::{NSAppearance, NSAppearanceCustomization, NSAppearanceNameDarkAqua};
use objc2_foundation::{
NSArray, NSDistributedNotificationCenter, NSNotification, NSObject, NSObjectProtocol, NSString,
};
use objc2_web_kit::WKWebView;
use super::dispatch_event;
#[must_use]
pub(crate) fn is_dark_view(view: &WKWebView) -> bool {
appearance_is_dark(&view.effectiveAppearance())
}
fn appearance_is_dark(appearance: &NSAppearance) -> bool {
let dark_aqua = unsafe { NSAppearanceNameDarkAqua };
appearance
.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[dark_aqua]))
.is_some()
}
pub(super) struct ThemeObserverIvars {
events: EventRegistry,
webview: Retained<WKWebView>,
#[cfg(feature = "native-services")]
controller: Option<std::sync::Arc<crate::native_theme::platform::Controller>>,
}
define_class!(
#[unsafe(super = NSObject)]
#[thread_kind = MainThreadOnly]
#[ivars = ThemeObserverIvars]
pub(super) struct DesktopThemeObserver;
unsafe impl NSObjectProtocol for DesktopThemeObserver {}
impl DesktopThemeObserver {
#[unsafe(method(desktopThemeChanged:))]
fn desktop_theme_changed(&self, _notification: &NSNotification) {
let ivars = self.ivars();
let dark = is_dark_view(&ivars.webview);
#[cfg(feature = "native-services")]
if let Some(controller) = &ivars.controller {
if !controller.observe(dark) {
return;
}
}
dispatch_event(&ivars.events, &ivars.webview, DesktopEvent::ThemeChanged { dark });
}
}
);
impl DesktopThemeObserver {
fn new(
mtm: MainThreadMarker,
events: EventRegistry,
webview: Retained<WKWebView>,
#[cfg(feature = "native-services")] controller: Option<
std::sync::Arc<crate::native_theme::platform::Controller>,
>,
) -> Retained<Self> {
let this = Self::alloc(mtm).set_ivars(ThemeObserverIvars {
events,
webview,
#[cfg(feature = "native-services")]
controller,
});
unsafe { msg_send![super(this), init] }
}
}
pub(super) fn install_theme_observer(
mtm: MainThreadMarker,
events: EventRegistry,
webview: Retained<WKWebView>,
#[cfg(feature = "native-services")] controller: Option<
std::sync::Arc<crate::native_theme::platform::Controller>,
>,
) -> Retained<DesktopThemeObserver> {
let observer = DesktopThemeObserver::new(
mtm,
events.clone(),
webview.clone(),
#[cfg(feature = "native-services")]
controller,
);
let center = NSDistributedNotificationCenter::defaultCenter();
let name = NSString::from_str("AppleInterfaceThemeChangedNotification");
unsafe {
center.addObserver_selector_name_object(
&observer,
sel!(desktopThemeChanged:),
Some(&name),
None,
);
}
dispatch_event(
&events,
&webview,
DesktopEvent::ThemeChanged {
dark: is_dark_view(&webview),
},
);
observer
}
#[cfg(test)]
#[allow(clippy::disallowed_methods)]
mod tests {
use super::*;
#[test]
fn dark_aqua_is_recognized_as_dark() {
let dark_aqua = unsafe { NSAppearanceNameDarkAqua };
let appearance = NSAppearance::appearanceNamed(dark_aqua).unwrap();
assert!(appearance_is_dark(&appearance));
let aqua = unsafe { objc2_app_kit::NSAppearanceNameAqua };
let appearance = NSAppearance::appearanceNamed(aqua).unwrap();
assert!(!appearance_is_dark(&appearance));
}
}