//! Windows 11 adapter: WinRT `UISettings` semantic colors with a //! `Personalize` registry fallback, watched through `ColorValuesChanged`. use windows::Foundation::TypedEventHandler; use windows::UI::ViewManagement::{AccessibilitySettings, UIColorType, UISettings}; use windows::Win32::Foundation::RPC_E_CHANGED_MODE; use windows::Win32::System::Com::{COINIT_APARTMENTTHREADED, CoInitializeEx}; use windows::core::IInspectable; use super::ThemeSource; use super::signal::ReapplySignal; use crate::ThemeVariant; use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, SystemTheme}; /// WinUI 11's `ControlCornerRadius`, in pixels at 100% scaling. const CONTROL_RADIUS_PX: f32 = 4.0; /// WinUI 11's default text-box height, in pixels at 100% scaling. const INPUT_HEIGHT_PX: f32 = 32.0; /// The window caption's height, in pixels at 100% scaling, as Win32 reports it. const TITLEBAR_HEIGHT_PX: f32 = 32.0; /// WinUI 11's default body text size, in pixels at 100% scaling. const ROOT_FONT_SIZE_PX: f32 = 14.0; /// The `Personalize` registry values, read once at startup as the fallback /// when the WinRT surface is unavailable. #[derive(Clone, Copy, Debug, Default)] struct RegistrySettings { apps_use_light_theme: Option, enable_transparency: Option, } impl RegistrySettings { fn read() -> Self { let personalize = || { windows_registry::CURRENT_USER .open(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize") }; Self { apps_use_light_theme: personalize() .and_then(|key| key.get_u32("AppsUseLightTheme")) .ok() .map(|value| value != 0), enable_transparency: personalize() .and_then(|key| key.get_u32("EnableTransparency")) .ok() .map(|value| value != 0), } } } pub(crate) struct WindowsSource { ui_settings: UISettings, accessibility_settings: AccessibilitySettings, registry: RegistrySettings, /// Keeps the main-thread channel loop alive. _signal: Option, } pub(crate) fn platform_source() -> Box { let initialized = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) }; if initialized.is_err() && initialized != RPC_E_CHANGED_MODE { eprintln!("theme: COM initialization failed: {initialized:?}"); } match WindowsSource::new() { Some(source) => Box::new(source), None => { eprintln!("theme: Windows system theme unavailable; using fallback"); Box::new(super::fallback::FallbackSource) } } } impl WindowsSource { fn new() -> Option { let ui_settings = match UISettings::new() { Ok(ui_settings) => ui_settings, Err(error) => { eprintln!("theme: UISettings unavailable: {error}"); return None; } }; let accessibility_settings = match AccessibilitySettings::new() { Ok(accessibility_settings) => accessibility_settings, Err(error) => { eprintln!("theme: AccessibilitySettings unavailable: {error}"); return None; } }; Some(Self { ui_settings, accessibility_settings, registry: RegistrySettings::read(), _signal: None, }) } fn color(&self, kind: UIColorType) -> Option { let color = self.ui_settings.GetColorValue(kind).ok()?; Some(gpui::Hsla::from(gpui::Rgba { r: color.R as f32 / 255.0, g: color.G as f32 / 255.0, b: color.B as f32 / 255.0, a: color.A as f32 / 255.0, })) } fn accessibility(&self) -> AccessibilityPreferences { AccessibilityPreferences { high_contrast: self.accessibility_settings.HighContrast().unwrap_or(false), // Windows states animations separately in Win32; not read yet. reduce_motion: false, reduce_transparency: self .registry .enable_transparency .map(|enabled| !enabled) .unwrap_or(false), } } } impl ThemeSource for WindowsSource { fn snapshot(&self, variant: ThemeVariant) -> SystemTheme { let accent = self.color(UIColorType::Accent); let background = self.color(UIColorType::Background); let foreground = self.color(UIColorType::Foreground); let variant = background .map(|background| { if background.l >= 0.5 { ThemeVariant::Light } else { ThemeVariant::Dark } }) .or(self.registry.apps_use_light_theme.map(|light| { if light { ThemeVariant::Light } else { ThemeVariant::Dark } })) .unwrap_or(variant); let text_scale = self.ui_settings.TextScaleFactor().unwrap_or(1.0); SystemTheme { variant, accent, // Windows states its ramp through UIColorType's accent variants. palette: PartialPalette { background, foreground, // Windows paints keyboard focus with the accent itself. ring: accent, ..PartialPalette::default() }, control_radius: Some(gpui::px(CONTROL_RADIUS_PX)), input_height: Some(gpui::px(INPUT_HEIGHT_PX)), titlebar_height: Some(gpui::px(TITLEBAR_HEIGHT_PX)), root_font_size: Some(gpui::px(ROOT_FONT_SIZE_PX * text_scale as f32)), accessibility: self.accessibility(), source: SourceKind::Windows, } } fn install_watchers(&mut self, async_cx: gpui::AsyncApp) { let signal = ReapplySignal::install(&async_cx); let color_signal = signal.sender(); let color_handler = TypedEventHandler::::new(move |_, _| { ReapplySignal::poke(&color_signal); Ok(()) }); if let Err(error) = self.ui_settings.ColorValuesChanged(&color_handler) { eprintln!("theme: color change watch failed: {error}"); } let contrast_signal = signal.sender(); let contrast_handler = TypedEventHandler::::new(move |_, _| { ReapplySignal::poke(&contrast_signal); Ok(()) }); if let Err(error) = self .accessibility_settings .HighContrastChanged(&contrast_handler) { eprintln!("theme: high contrast watch failed: {error}"); } self._signal = Some(signal); } fn label(&self) -> &'static str { SourceKind::Windows.label() } }