43

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?

c-smile
  • 26,734
  • 7
  • 59
  • 86

5 Answers5

46

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.

Mike
  • 14,010
  • 29
  • 101
  • 161
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • 1
    Any way to subscribe to changes, short of polling this registry value every x seconds? – Sören Kuklau Oct 14 '18 at 19:24
  • 3
    @SörenKuklau [RegNotifyChangeKeyValue](https://learn.microsoft.com/en-us/windows/desktop/api/Winreg/nf-winreg-regnotifychangekeyvalue) – user7860670 Oct 14 '18 at 20:51
  • 14
    @SörenKuklau Better way is to listen for [`WM_WININICHANGE` broadcast](https://learn.microsoft.com/en-us/windows/desktop/winmsg/wm-wininichange) - `lParam == "ImmersiveColorSet"`. – Martin Prikryl Dec 06 '18 at 09:04
  • 1
    Isn't [WM_SETTINGCHANGE](https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-settingchange) a better message to listen to? – dgellow Jan 18 '22 at 10:37
  • 4
    @dgellow Yes, but they're both `#define`d as the same value, so it doesn't matter at the compiler level. – sidbushes Jan 27 '22 at 16:42
9

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);
}
jarjar
  • 1,681
  • 2
  • 15
  • 19
  • 7
    winrt is not win32 and the question was about specifically win32. – c-smile Apr 14 '19 at 19:11
  • 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. – jarjar Apr 20 '19 at 23:47
  • 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. – c-smile Apr 21 '19 at 19:58
  • 5
    @c-smile No accident, microsoft exposes all its UWP stuff to C++ apps via winrt. The [examples on the UISettings reference shows C++ code](https://learn.microsoft.com/en-us/uwp/api/Windows.UI.ViewManagement.UISettings) – gbjbaanb May 01 '19 at 16:39
  • Except, types in the Windows Runtime, that are available to both UWP and Desktop applications have the [DualApiPartitionAttribute](https://learn.microsoft.com/en-us/uwp/api/windows.foundation.metadata.dualapipartitionattribute) assigned. The [UISettings](https://learn.microsoft.com/en-us/uwp/api/windows.ui.viewmanagement.uisettings) class does not. Is that an oversight in the API? A documentation bug? Or an unsupported scenario, that occasionally appears to work? – IInspectable Sep 02 '19 at 13:24
  • How can I link to this? Which .lib should I add to the linker's input in Visual Studio? – H. Al-Amri Oct 07 '19 at 17:46
  • 2
    @H.Al-Amri `WindowsApp.lib` contains everything you need. – Mark Ingram Jul 23 '20 at 13:25
9

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++

Rob
  • 45,296
  • 24
  • 122
  • 150
  • is this included in .net framework? I dont want to install anything – Egon Stetmann. Jun 09 '20 at 17:03
  • According to the description of the NuGet package `Microsoft.Windows.SDK.Contracts` .Net Framework 4.6+ is required not 4.5+. – Robert Jul 30 '20 at 08:15
  • @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 – Rob Jul 30 '20 at 09:18
9

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 _)
    }
}
dgellow
  • 692
  • 1
  • 11
  • 18
2

Here is a C# solution for the answer of @user7860670

using Microsoft.Win32;

try
{
    int res = (int)Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "AppsUseLightTheme", -1);
}
catch 
{
 //Exception Handling     
}
    

res contains the value for the default theme on windows

0 : dark theme

1 : light theme

-1 : AppsUseLightTheme could not be found

Rena821
  • 149
  • 7