How to detect Windows 10 light/dark mode in Win32 application?

15,126

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 _)
    }
}
Share:
15,126
c-smile
Author by

c-smile

Experienced kamikaze. MS in Applied Physics &amp; 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, 2022

Comments

  • c-smile
    c-smile about 2 years

    A bit of context: Sciter (pure win32 application) is already capable to render UWP alike UIs:

    in dark mode: in dark mode

    in light mode: in light mode

    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
    Sören Kuklau over 5 years
    Any way to subscribe to changes, short of polling this registry value every x seconds?
  • user7860670
    user7860670 over 5 years
    @SörenKuklau RegNotifyChangeKeyValue
  • Martin Prikryl
    Martin Prikryl over 5 years
    @SörenKuklau Better way is to listen for WM_WININICHANGE broadcast - lParam == "ImmersiveColorSet".
  • c-smile
    c-smile about 5 years
    winrt is not win32 and the question was about specifically win32.
  • jarjar
    jarjar about 5 years
    The 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
    c-smile about 5 years
    According 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
    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
    IInspectable almost 5 years
    Except, 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
    H. Al-Amri over 4 years
    How can I link to this? Which .lib should I add to the linker's input in Visual Studio?
  • Egon Stetmann.
    Egon Stetmann. about 4 years
    is this included in .net framework? I dont want to install anything
  • Adam McKee
    Adam McKee almost 4 years
    @H.Al-Amri WindowsApp.lib contains everything you need.
  • Robert
    Robert almost 4 years
    According to the description of the NuGet package Microsoft.Windows.SDK.Contracts .Net Framework 4.6+ is required not 4.5+.
  • Rob
    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
    dgellow over 2 years
    Isn't WM_SETTINGCHANGE a better message to listen to?
  • sidbushes
    sidbushes over 2 years
    @dgellow Yes, but they're both #defined as the same value, so it doesn't matter at the compiler level.