How to detect Windows 10 light/dark mode in Win32 application?
Solution 1
Well, it looks like this option is not exposed to regular Win32 applications directly, however it can be set / retrieved through the AppsUseLightTheme
key at the HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
registry path.
Solution 2
The Microsoft.Windows.SDK.Contracts
NuGet package gives .NET Framework 4.5+ and .NET Core 3.0+ applications access to Windows 10 WinRT APIs, including Windows.UI.ViewManagement.Settings
mentioned in the answer by jarjar. With this package added to a .NET Core 3.0 console app that consists of this code:
using System;
using Windows.UI.ViewManagement;
namespace WhatColourAmI
{
class Program
{
static void Main(string[] args)
{
var settings = new UISettings();
var foreground = settings.GetColorValue(UIColorType.Foreground);
var background = settings.GetColorValue(UIColorType.Background);
Console.WriteLine($"Foreground {foreground} Background {background}");
}
}
}
The output when the theme is set to Dark is:
Foreground #FFFFFFFF Background #FF000000
When the theme is set to Light it's:
Foreground #FF000000 Background #FFFFFFFF
As this is exposed via a Microsoft provided package that states:
This package includes all the supported Windows Runtime APIs up to Windows 10 version 1903
It's a pretty safe bet that it's intentional that this API is accessible!
Note: This isn't explicitly checking whether the theme is Light or Dark but checking for a pair of values that suggest that the theme in use is one of the two, so,.. the correctness of this method is mildly questionable but it's at least a "pure" C# way of achieving what's been outlined elsewhere with C++
Solution 3
EDIT: Calling out that this works in all Win32 projects as long as you're building with c++17 enabled.
If you're using the latest SDK, this worked for me.
#include <winrt/Windows.UI.ViewManagement.h>
using namespace winrt::Windows::UI::ViewManagement;
if (RUNNING_ON_WINDOWS_10) {
UISettings settings;
auto background = settings.GetColorValue(UIColorType::Background);
auto foreground = settings.GetColorValue(UIColorType::Foreground);
}
Solution 4
To add to the solution suggested by @user7860670, i.e: checking the registry key AppsUseLightTheme
, I think it is worth having some code example.
To read from the registry Win32 has RegGetValue.
C++
bool is_light_theme() {
// based on https://stackoverflow.com/questions/51334674/how-to-detect-windows-10-light-dark-mode-in-win32-application
// The value is expected to be a REG_DWORD, which is a signed 32-bit little-endian
auto buffer = std::vector<char>(4);
auto cbData = static_cast<DWORD>(buffer.size() * sizeof(char));
auto res = RegGetValueW(
HKEY_CURRENT_USER,
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
L"AppsUseLightTheme",
RRF_RT_REG_DWORD, // expected value type
nullptr,
buffer.data(),
&cbData);
if (res != ERROR_SUCCESS) {
throw std::runtime_error("Error: error_code=" + std::to_string(res));
}
// convert bytes written to our buffer to an int, assuming little-endian
auto i = int(buffer[3] << 24 |
buffer[2] << 16 |
buffer[1] << 8 |
buffer[0]);
return i == 1;
}
Rust
Using the windows-rs projection crate:
pub fn is_light_theme() -> bool {
// based on https://stackoverflow.com/a/51336913/709884
let mut buffer: [u8; 4] = [0; 4];
let mut cb_data: u32 = (buffer.len()).try_into().unwrap();
let res = unsafe {
RegGetValueW(
HKEY_CURRENT_USER,
r#"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"#
.to_wide()
.as_pwstr(),
"AppsUseLightTheme".to_wide().as_pwstr(),
RRF_RT_REG_DWORD,
std::ptr::null_mut(),
buffer.as_mut_ptr() as _,
&mut cb_data as *mut _,
)
};
assert_eq!(
res,
ERROR_SUCCESS,
format!("failed to read key from registry: err_code={}", res).as_str(),
);
// REG_DWORD is signed 32-bit, using little endian
let light_mode = i32::from_le_bytes(buffer) == 1;
light_mode
}
pub fn is_dark_theme() -> bool {
!is_light_theme()
}
// convert &str to Win32 PWSTR
#[derive(Default)]
pub struct WideString(pub Vec<u16>);
pub trait ToWide {
fn to_wide(&self) -> WideString;
}
impl ToWide for &str {
fn to_wide(&self) -> WideString {
let mut result: Vec<u16> = self.encode_utf16().collect();
result.push(0);
WideString(result)
}
}
impl ToWide for String {
fn to_wide(&self) -> WideString {
let mut result: Vec<u16> = self.encode_utf16().collect();
result.push(0);
WideString(result)
}
}
impl WideString {
pub fn as_pwstr(&self) -> PWSTR {
PWSTR(self.0.as_ptr() as *mut _)
}
}
![c-smile](https://i.stack.imgur.com/l4J7H.jpg?s=256&g=1)
c-smile
Experienced kamikaze. MS in Applied Physics & Mathematics. Author of Sciter - HTML/CSS/scripting engine for modern desktop applications. Sciter Notes - personal notes and document manager. HTML Notepad - Simple and fast WYSIWYM HTML editor. Used to be Invited Expert at W3C HTML5 working group at the time of HTML5 spec development. c-smile @ github c-smile @ codeproject UI design in all its incarnations: desktop and mobile, native and web, Windows and OSX, Linux and its variations.
Updated on June 20, 2022Comments
-
c-smile about 2 years
A bit of context: Sciter (pure win32 application) is already capable to render UWP alike UIs:
Windows 10.1803 introduces Dark/Light switch in Settings applet as seen here for example.
Question: how do I determine current type of that "app mode" in Win32 application?
-
Sören Kuklau over 5 yearsAny way to subscribe to changes, short of polling this registry value every x seconds?
-
user7860670 over 5 years@SörenKuklau RegNotifyChangeKeyValue
-
Martin Prikryl over 5 years@SörenKuklau Better way is to listen for
WM_WININICHANGE
broadcast -lParam == "ImmersiveColorSet"
. -
c-smile about 5 yearswinrt is not win32 and the question was about specifically win32.
-
jarjar about 5 yearsThe reason I posted this was because it does indeed work with win32 applications on Windows 10. It does not require special permission or hacking into the registry.
-
c-smile about 5 yearsAccording to MSDN: "winrt::Windows::UI::ViewManagement - Provides support for handling and managing the various views associated with the active Universal Windows Platform (UWP) app." If it works in Win32 applications than by accident that can be fixed at any time.
-
gbjbaanb about 5 years@c-smile No accident, microsoft exposes all its UWP stuff to C++ apps via winrt. The examples on the UISettings reference shows C++ code
-
IInspectable almost 5 yearsExcept, types in the Windows Runtime, that are available to both UWP and Desktop applications have the DualApiPartitionAttribute assigned. The UISettings class does not. Is that an oversight in the API? A documentation bug? Or an unsupported scenario, that occasionally appears to work?
-
H. Al-Amri over 4 yearsHow can I link to this? Which .lib should I add to the linker's input in Visual Studio?
-
Egon Stetmann. about 4 yearsis this included in .net framework? I dont want to install anything
-
Adam McKee almost 4 years@H.Al-Amri
WindowsApp.lib
contains everything you need. -
Robert almost 4 yearsAccording to the description of the NuGet package
Microsoft.Windows.SDK.Contracts
.Net Framework 4.6+ is required not 4.5+. -
Rob almost 4 years@Robert, the latest version of the package available at the time of writing required 4.5+, it appears that this changed as of mid-May this year when 10.0.19041.1 was released
-
dgellow over 2 yearsIsn't WM_SETTINGCHANGE a better message to listen to?
-
sidbushes over 2 years@dgellow Yes, but they're both
#define
d as the same value, so it doesn't matter at the compiler level.