Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 949
c-smile Avatar asked Jul 14 '18 02:07

c-smile


People also ask

How do I force an app to dark in Windows 10?

To enable dark mode, navigate to Settings > Personalization > Colors, then open the drop-down menu for "Choose your color" and pick Dark. Dark (and Light) mode change the look of the Windows Start menu and built-in apps.

How do I show dark mode?

Open your device's Settings app . Select Accessibility. Under "Display," turn on Dark theme.

How do I change Microsoft from dark to Light?

Select Start > Settings . Select Personalization > Colors. Under Choose your color, select Light.

Do users prefer Light or dark mode?

Users want to opt for Dark Mode in applications According to a survey carried out by Android Authority with 2,500 Android users, 81.9% use Dark Mode on their phones, in apps and anywhere else available. 9,9% said they switch between Dark and Light Mode.


4 Answers

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.

like image 116
user7860670 Avatar answered Oct 17 '22 22:10

user7860670


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

like image 30
Rob Avatar answered Oct 17 '22 22:10

Rob


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);
}
like image 8
jarjar Avatar answered Oct 17 '22 22:10

jarjar


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 _)
    }
}
like image 4
dgellow Avatar answered Oct 17 '22 23:10

dgellow