system_theme/platform/
windows.rs

1use std::sync::Arc;
2use tokio::sync::Notify;
3use windows::{
4    core::HSTRING,
5    Foundation::{Metadata::ApiInformation, TypedEventHandler},
6    UI::{
7        Color,
8        ViewManagement::{AccessibilitySettings, UIColorType, UISettings},
9    },
10};
11
12use crate::{error::Error, ThemeColor, ThemeContrast, ThemeKind, ThemeScheme};
13
14impl From<Color> for ThemeColor {
15    fn from(color: Color) -> Self {
16        ThemeColor {
17            red: color.R as f32 / 255.0,
18            green: color.G as f32 / 255.0,
19            blue: color.B as f32 / 255.0,
20        }
21    }
22}
23
24// Check if GetColorValue is supported
25fn check_color_supported() -> Result<bool, Error> {
26    const GET_COLOR_VALUE_TYPE: &str = "Windows.UI.ViewManagement.UISettings";
27    const GET_COLOR_VALUE_METHOD: &str = "GetColorValue";
28
29    ApiInformation::IsMethodPresent(
30        &HSTRING::from(GET_COLOR_VALUE_TYPE),
31        &HSTRING::from(GET_COLOR_VALUE_METHOD),
32    )
33    .map_err(Error::from_platform)
34}
35
36// Check if GetColorValue is supported
37fn check_high_contrast_supported() -> Result<bool, Error> {
38    const GET_HIGH_CONTRAST_TYPE: &str = "Windows.UI.ViewManagement.AccessibilitySettings";
39    const GET_HIGH_CONTRAST_VALUE_METHOD: &str = "HighContrast";
40
41    ApiInformation::IsPropertyPresent(
42        &HSTRING::from(GET_HIGH_CONTRAST_TYPE),
43        &HSTRING::from(GET_HIGH_CONTRAST_VALUE_METHOD),
44    )
45    .map_err(Error::from_platform)
46}
47
48pub struct Platform {
49    ui_settings: Option<UISettings>,
50    a11y_settings: Option<AccessibilitySettings>,
51    notify: Arc<Notify>,
52}
53
54impl Platform {
55    pub fn new() -> Result<Self, Error> {
56        let notify = Arc::new(Notify::new());
57
58        // Check if GetColorValue is supported
59        let ui_settings = if check_color_supported()? {
60            let ui_settings = UISettings::new().map_err(Error::from_platform)?;
61
62            // Create change watcher (ignore errors, not that important)
63            let notify_cloned = notify.clone();
64            let _ = ui_settings.ColorValuesChanged(&TypedEventHandler::new(move |_, _| {
65                notify_cloned.notify_waiters();
66                Ok(())
67            }));
68
69            Some(ui_settings)
70        } else {
71            None
72        };
73
74        // Check if HighContrast is supported
75        let a11y_settings = if check_high_contrast_supported()? {
76            Some(AccessibilitySettings::new().map_err(Error::from_platform)?)
77        } else {
78            None
79        };
80
81        Ok(Platform {
82            ui_settings,
83            a11y_settings,
84            notify,
85        })
86    }
87
88    pub fn theme_kind(&self) -> Result<ThemeKind, Error> {
89        Ok(ThemeKind::Windows)
90    }
91
92    pub fn theme_scheme(&self) -> Result<ThemeScheme, Error> {
93        // Get the background color reported by windows and check if dark
94        let background = self.get_ui_color(UIColorType::Background)?;
95
96        // Simple way for checking if it is dark. Windows returns #000 or #FFF anyways.
97        let color_sum = background.R as u16 + background.G as u16 + background.B as u16;
98        Ok(if color_sum < 3 * 128 {
99            ThemeScheme::Dark
100        } else {
101            ThemeScheme::Light
102        })
103    }
104
105    pub fn theme_contrast(&self) -> Result<ThemeContrast, Error> {
106        // Check if high contrast mode is enabled (if supported)
107        self.a11y_settings
108            .as_ref()
109            .map(|settings| {
110                settings
111                    .HighContrast()
112                    .map(|high_contrast| {
113                        if high_contrast {
114                            ThemeContrast::High
115                        } else {
116                            ThemeContrast::Normal
117                        }
118                    })
119                    .map_err(Error::from_platform)
120            })
121            .unwrap_or(Err(Error::Unsupported))
122    }
123
124    pub fn theme_accent(&self) -> Result<ThemeColor, Error> {
125        // Get main accent color. Ignoring accent shades for now.
126        self.get_ui_color(UIColorType::Accent)
127            .map(|color| color.into())
128    }
129
130    pub fn get_notify(&self) -> Arc<Notify> {
131        self.notify.clone()
132    }
133
134    fn get_ui_color(&self, color_type: UIColorType) -> Result<Color, Error> {
135        // Get the color reported by windows (if supported)
136        self.ui_settings
137            .as_ref()
138            .map(|settings| {
139                settings
140                    .GetColorValue(color_type)
141                    .map_err(Error::from_platform)
142            })
143            .unwrap_or(Err(Error::Unsupported))
144    }
145}