Skip to main content

winit/platform_impl/windows/
dark_mode.rs

1/// This is a simple implementation of support for Windows Dark Mode,
2/// which is inspired by the solution in https://github.com/ysc3839/win32-darkmode
3use std::{ffi::c_void, ptr};
4
5use crate::utils::Lazy;
6use windows_sys::core::PCSTR;
7use windows_sys::Win32::Foundation::{BOOL, HWND, NTSTATUS, S_OK};
8use windows_sys::Win32::System::LibraryLoader::{GetProcAddress, LoadLibraryA};
9use windows_sys::Win32::System::SystemInformation::OSVERSIONINFOW;
10use windows_sys::Win32::UI::Accessibility::{HCF_HIGHCONTRASTON, HIGHCONTRASTA};
11use windows_sys::Win32::UI::Controls::SetWindowTheme;
12use windows_sys::Win32::UI::WindowsAndMessaging::{SystemParametersInfoA, SPI_GETHIGHCONTRAST};
13
14use crate::window::Theme;
15
16use super::util;
17
18static WIN10_BUILD_VERSION: Lazy<Option<u32>> = Lazy::new(|| {
19    type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> NTSTATUS;
20    let handle = get_function!("ntdll.dll", RtlGetVersion);
21
22    if let Some(rtl_get_version) = handle {
23        unsafe {
24            let mut vi = OSVERSIONINFOW {
25                dwOSVersionInfoSize: 0,
26                dwMajorVersion: 0,
27                dwMinorVersion: 0,
28                dwBuildNumber: 0,
29                dwPlatformId: 0,
30                szCSDVersion: [0; 128],
31            };
32
33            let status = (rtl_get_version)(&mut vi);
34
35            if status >= 0 && vi.dwMajorVersion == 10 && vi.dwMinorVersion == 0 {
36                Some(vi.dwBuildNumber)
37            } else {
38                None
39            }
40        }
41    } else {
42        None
43    }
44});
45
46static DARK_MODE_SUPPORTED: Lazy<bool> = Lazy::new(|| {
47    // We won't try to do anything for windows versions < 17763
48    // (Windows 10 October 2018 update)
49    match *WIN10_BUILD_VERSION {
50        Some(v) => v >= 17763,
51        None => false,
52    }
53});
54
55static DARK_THEME_NAME: Lazy<Vec<u16>> = Lazy::new(|| util::encode_wide("DarkMode_Explorer"));
56static LIGHT_THEME_NAME: Lazy<Vec<u16>> = Lazy::new(|| util::encode_wide(""));
57
58/// Attempt to set a theme on a window, if necessary.
59/// Returns the theme that was picked
60pub fn try_theme(hwnd: HWND, preferred_theme: Option<Theme>) -> Theme {
61    if *DARK_MODE_SUPPORTED {
62        let is_dark_mode = match preferred_theme {
63            Some(theme) => theme == Theme::Dark,
64            None => should_use_dark_mode(),
65        };
66
67        let theme = if is_dark_mode { Theme::Dark } else { Theme::Light };
68        let theme_name = match theme {
69            Theme::Dark => DARK_THEME_NAME.as_ptr(),
70            Theme::Light => LIGHT_THEME_NAME.as_ptr(),
71        };
72
73        let status = unsafe { SetWindowTheme(hwnd, theme_name, ptr::null()) };
74
75        if status == S_OK && set_dark_mode_for_window(hwnd, is_dark_mode) {
76            return theme;
77        }
78    }
79
80    Theme::Light
81}
82
83fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool {
84    // Uses Windows undocumented API SetWindowCompositionAttribute,
85    // as seen in win32-darkmode example linked at top of file.
86
87    type SetWindowCompositionAttribute =
88        unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
89
90    #[allow(clippy::upper_case_acronyms)]
91    type WINDOWCOMPOSITIONATTRIB = u32;
92    const WCA_USEDARKMODECOLORS: WINDOWCOMPOSITIONATTRIB = 26;
93
94    #[allow(non_snake_case)]
95    #[allow(clippy::upper_case_acronyms)]
96    #[repr(C)]
97    struct WINDOWCOMPOSITIONATTRIBDATA {
98        Attrib: WINDOWCOMPOSITIONATTRIB,
99        pvData: *mut c_void,
100        cbData: usize,
101    }
102
103    static SET_WINDOW_COMPOSITION_ATTRIBUTE: Lazy<Option<SetWindowCompositionAttribute>> =
104        Lazy::new(|| get_function!("user32.dll", SetWindowCompositionAttribute));
105
106    if let Some(set_window_composition_attribute) = *SET_WINDOW_COMPOSITION_ATTRIBUTE {
107        unsafe {
108            // SetWindowCompositionAttribute needs a bigbool (i32), not bool.
109            let mut is_dark_mode_bigbool = BOOL::from(is_dark_mode);
110
111            let mut data = WINDOWCOMPOSITIONATTRIBDATA {
112                Attrib: WCA_USEDARKMODECOLORS,
113                pvData: &mut is_dark_mode_bigbool as *mut _ as _,
114                cbData: std::mem::size_of_val(&is_dark_mode_bigbool) as _,
115            };
116
117            let status = set_window_composition_attribute(hwnd, &mut data);
118
119            status != false.into()
120        }
121    } else {
122        false
123    }
124}
125
126pub fn should_use_dark_mode() -> bool {
127    should_apps_use_dark_mode() && !is_high_contrast()
128}
129
130fn should_apps_use_dark_mode() -> bool {
131    type ShouldAppsUseDarkMode = unsafe extern "system" fn() -> bool;
132    static SHOULD_APPS_USE_DARK_MODE: Lazy<Option<ShouldAppsUseDarkMode>> = Lazy::new(|| unsafe {
133        const UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL: PCSTR = 132 as PCSTR;
134
135        // We won't try to do anything for windows versions < 17763
136        // (Windows 10 October 2018 update)
137        if !*DARK_MODE_SUPPORTED {
138            return None;
139        }
140
141        let module = LoadLibraryA("uxtheme.dll\0".as_ptr().cast());
142
143        if module == 0 {
144            return None;
145        }
146
147        let handle = GetProcAddress(module, UXTHEME_SHOULDAPPSUSEDARKMODE_ORDINAL);
148
149        handle.map(|handle| std::mem::transmute(handle))
150    });
151
152    SHOULD_APPS_USE_DARK_MODE
153        .map(|should_apps_use_dark_mode| unsafe { (should_apps_use_dark_mode)() })
154        .unwrap_or(false)
155}
156
157fn is_high_contrast() -> bool {
158    let mut hc = HIGHCONTRASTA { cbSize: 0, dwFlags: 0, lpszDefaultScheme: ptr::null_mut() };
159
160    let ok = unsafe {
161        SystemParametersInfoA(
162            SPI_GETHIGHCONTRAST,
163            std::mem::size_of_val(&hc) as _,
164            &mut hc as *mut _ as _,
165            0,
166        )
167    };
168
169    ok != false.into() && util::has_flag(hc.dwFlags, HCF_HIGHCONTRASTON)
170}