Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: Dark mode for applications #41

Open
sylveon opened this issue May 21, 2020 · 91 comments
Open

Discussion: Dark mode for applications #41

sylveon opened this issue May 21, 2020 · 91 comments

Comments

@sylveon
Copy link

sylveon commented May 21, 2020

Discussion: Dark mode for applications

Currently, reliably detecting dark mode in Win32 applications requires either reading the registry or using undocumented methods from uxtheme.dll.

I strongly suggest to document those two existing methods:

  • ShouldAppsUseDarkMode:
    • This allows apps to detect whether the dark theme is used for apps.
    • Particularly useful to follow system settings, like Microsoft Edge and UWP-based apps do.
    • Changes to this can be detected by listening for WM_SETTINGCHANGE.
  • ShouldSystemUseDarkMode:
    • This allows apps to detect whether the taskbar, start menu and other system shell elements uses the dark theme or not.
    • Particularly useful for determining if a white or black notification area icon should be used. OneDrive already does this.
    • Changes to this can be detected by listening for WM_SETTINGCHANGE.

This will allow existing apps to easily implement dark theme support, and to follow system settings without relying on workarounds. A WinRT API should probably be exposed (the two settings and an event to listen for changes) as well, but some applications are already tentatively using those two APIs, so it would be the best case to document the Win32 APIs, as those won't have to do any significant work to adopt an official solution rather than an undocumented one (and, truth be told, they are much simpler to use than their WinRT counterparts, especially if your language doesn't have a WinRT projection available)

Also, there exists other APIs in uxtheme.dll which might be of interest for people transitioning to WinUI and/or XAML islands:

  • SetPreferredAppMode:
    • This allows apps to enable dark theme support for Win32 controls. This is notably used by Windows Explorer for its dark theme.
  • AllowDarkModeForWindow:
    • Once the app mode is set to AllowDark using the API above, it is a per-window opt-in.
    • Once this method is called, the Win32 controls in the window uses dark theme if the system dark theme is enabled, and automatically switches to light theme when the user changes their settings.
    • Note that some controls might need to have their theme manually set to DarkMode_Explorer for this to be effective
    • Dark mode ribbon can be opt-in with the window property UI_PKEY_DarkModeRibbon

Those methods are useful because when transitioning, it allows developers to use a dark theme for old controls and new controls alike, to get a somewhat consistent UI. Darkening Win32 or winforms controls manually is extremely hard to get right, and leveraging the work done in Windows Explorer would prevent a lot of misdirected attempts at dark theming Win32 controls.

EVEN MORE, there's an undocumented window attribute allowing dark mode title bars (DWMWA_USE_IMMERSIVE_DARK_MODE). This one was even used by the command prompt itself and openly on GitHub, before it got removed from the OSS version because it was internal (see microsoft/terminal@bc7eb96#diff-e26a93b2aa9fea92ebf24336c4fe6412L19-L22). No true dark mode comes without a dark titlebar, and customizing the titlebar is a complex endeavour that would be greatly simplified by the publication of this attribute.

All of the APIs mentioned have been added since the introduction of dark mode Explorer, and have proven to be stable or only very slightly modified, so I'm sad they are being kept private because it would allow so many apps to get a dark mode (light mode makes me cry 😢)

@jonwis
Copy link
Member

jonwis commented May 21, 2020

Thanks! Project Reunion APIs will be defined in metadata like WinRT objects are so they can be projected to all languages and all runtimes.

Can you mock up what an API for this would look like? Maybe something like:

enum WindowThemePreferenceColorMode {
    None = 0,
    Dark,
    Light
}
runtimeclass WindowThemePreference {
    static WindowThemePreferenceColorMode SystemColorMode { get; };
    static WindowThemePreferenceColorMode AppColorMode { get; };
    static event EventHandler<Object> PreferenceChanged;

    static void SetAppPreferredColorMode(WindowThemePreferenceColorMode mode);
    static void SetWindowPreferredColorMode(WindowId window, WindowThemePreferenceColorMode mode);
    static void SetWindowTitleBarPreferredColorMode(WindowId window, WindowThemePreferenceColorMode mode);
}

@mdtauk
Copy link

mdtauk commented May 21, 2020

Ideally it would remain a RequestedTheme = Light/Dark value in the App.xaml or Window Xaml element. The Window Frame and TitleBar reflecting that setting, or if it is not set, the System setting.

@sylveon
Copy link
Author

sylveon commented May 21, 2020

My suggestion is for non-XAML code, so RequestedTheme is not a thing. Currently, this code has no reliable way to detect the theme, and I would very much like to be able to follow user preferences for non-XAML parts of my app that is using XAML Islands (eg. tray icon and its context menu).

I can't adopt WinUI's UWP app model "clone" intended for desktop apps either without a significant rewrite. C++/WinRT is already causing compilation time and error headaches even when only a small part of my code is using it, so I would actually rather not adopt it at all (not to mention that IDL is a complete PITA).

If a WinRT API for this only supports WinUI desktop app scenarios, it would be useless to me, and I'll just use the undocumented functions I listed above.

I would suggest documenting the OS APIs that I listed (and already exist as well as being usable in uxtheme.dll today, they are just not documented and exported by ordinal only) in some Windows SDK update, because it would reach a wider audience (all Win32 devs) than exposing this through only a WinRT API would (only early adopters of Project Reunion, and people whose language of choice includes a WinRT projection)

For example, a Delphi-based Win32 app (you know those still exist) or a C++ app using an older version of the C++ compiler (like VS2013) could use ShouldAppsUseDarkMode trivially but it will be much harder to use a WinRT API for either of those apps.

This might not be in the scope of Project Reunion itself, but it's where I was told to file feedback about it.

@sylveon
Copy link
Author

sylveon commented May 21, 2020

If this isn't the place to request raw Win32 APIs, feel free to redirect me to the right place (except if that place is the Feedback Hub, my confidence in it has been reduced to 0)

@jonwis
Copy link
Member

jonwis commented May 21, 2020

Project Reunion is about making APIs available to all apps - no matter which language, UX framework, runtime, or packaging system you use. WinUI-specific APIs would go in the WinUI repo.

We're still planning out which language projections to add - issue #18 has a proposed projection of an IDL based type for "flat C." Can you add comments / bumps to it so we can get a sense of which projections are important? See also issues tagged with "projection" for others folks have asked for.

Some Project Reunion APIs will also be "flat C to start" (ie: direct exports from the DLL with an associated header & import library) and then also get a metadata wrapper for languages & runtimes whose FFI is cumbersome.

Can you also file an issue in the cppwinrt repo for compilation times? (@kennykerr)

@sylveon
Copy link
Author

sylveon commented May 21, 2020

Of course, I'm not opposed to providing a WinRT projection as well, I just believe that stabilizing the Win32 APIs would be best, as there are already existing users of those in the wild, and providing a guarantee the API won't break or be removed from under their feet would be best. It's also much simpler for those who can't use existing projections to use those.

Documenting and then wrapping those APIs in a WinRT class would also take less time than reimplementing them in WinRT.

@sylveon
Copy link
Author

sylveon commented May 21, 2020

As for the compilation times, it's more of an inherent issue with C++ compilers, should be somewhat fixed when modules support is enabled in C++/WinRT.

@jonwis
Copy link
Member

jonwis commented Jul 13, 2020

Aha! Check out the UISettings type from your Win32 apps, like this (C++/WinRT) example:

winrt::Windows::UI::ViewManagement::UISettings settings;
auto fg = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Foreground);
auto bg = settings.GetColorValue(winrt::Windows::UI::ViewManagement::UIColorType::Background);

Apps can listen to changes to this setting on the UISettings.ColorValuesChanged event. There's also the (missing a verb) UIElementColor method, which you can for specific colors of UX components.

While you can't ask "are you dark/light/custom mode", you can definitely get the set of colors used by Windows to theme its own UX elements, which would let your apps be consistent.

@sylveon
Copy link
Author

sylveon commented Jul 14, 2020

That certainly can work if you do your own custom drawing, but it doesn't cover cases where your UI kit has a simple dark/light toggle, where you have to guesstimate if bg is dark or light, neither does it cover the case where your app uses common controls or context menus with no custom drawing.

For example, XAML islands supports dark/light theming according to user preferences, but it doesn't update the theme when the user changes it in settings, so when my app receives WM_SETTINGCHANGE, I manually call the undocumented ShouldAppsUseDarkMode function and update the RequestedTheme on the XAML content appropriately:

https://github.com/TranslucentTB/TranslucentTB/blob/d8fa18512b11405bdacf93d463fb4b500de69b62/TranslucentTB/uwp/xamlpagehost.hpp#L59-L70

Also, if your app has a tray icon, you may want to know if the system uses a light or dark theme (not the apps), to choose the appropriate tray icon color (a white tray icon for the dark theme, and a black tray icon for the light theme)

@rgwood
Copy link

rgwood commented Jul 29, 2021

I'm getting ready to support Windows 11 in a Win32 app, and the Windows 11 developer documentation encourages developers to "Support Dark and Light themes."

My app's styling needs are very minimal - just the title bar - but it seems I will still need to resort to undocumented APIs. I hope this can be resolved soon.

@ghost
Copy link

ghost commented Jul 30, 2021

My app's styling needs are very minimal - just the title bar - but it seems I will still need to resort to undocumented APIs.

DWMWA_CAPTION_COLOR
https://twitter.com/zodiacon/status/1416734060278341633

@zodiacon
Copy link

zodiacon commented Aug 4, 2021

It's not undocumented, I found it in the latest insider preview SDK (build 22000).

@mveril
Copy link

mveril commented Aug 4, 2021

@zodiacon yes, for information bellow this is the DWMWINDOWATTRIBUTE flags diff in the Windows 11 (Version 10.0.22000.0) preview SDK

enum DWMWINDOWATTRIBUTE
{
    DWMWA_NCRENDERING_ENABLED = 1,              // [get] Is non-client rendering enabled/disabled
    DWMWA_NCRENDERING_POLICY,                   // [set] DWMNCRENDERINGPOLICY - Non-client rendering policy
    DWMWA_TRANSITIONS_FORCEDISABLED,            // [set] Potentially enable/forcibly disable transitions
    DWMWA_ALLOW_NCPAINT,                        // [set] Allow contents rendered in the non-client area to be visible on the DWM-drawn frame.
    DWMWA_CAPTION_BUTTON_BOUNDS,                // [get] Bounds of the caption button area in window-relative space.
    DWMWA_NONCLIENT_RTL_LAYOUT,                 // [set] Is non-client content RTL mirrored
    DWMWA_FORCE_ICONIC_REPRESENTATION,          // [set] Force this window to display iconic thumbnails.
    DWMWA_FLIP3D_POLICY,                        // [set] Designates how Flip3D will treat the window.
    DWMWA_EXTENDED_FRAME_BOUNDS,                // [get] Gets the extended frame bounds rectangle in screen space
    DWMWA_HAS_ICONIC_BITMAP,                    // [set] Indicates an available bitmap when there is no better thumbnail representation.
    DWMWA_DISALLOW_PEEK,                        // [set] Don't invoke Peek on the window.
    DWMWA_EXCLUDED_FROM_PEEK,                   // [set] LivePreview exclusion information
    DWMWA_CLOAK,                                // [set] Cloak or uncloak the window
    DWMWA_CLOAKED,                              // [get] Gets the cloaked state of the window
    DWMWA_FREEZE_REPRESENTATION,                // [set] BOOL, Force this window to freeze the thumbnail without live update
    DWMWA_PASSIVE_UPDATE_MODE,                  // [set] BOOL, Updates the window only when desktop composition runs for other reasons
+   DWMWA_USE_HOSTBACKDROPBRUSH,                // [set] BOOL, Allows the use of host backdrop brushes for the window.
+   DWMWA_USE_IMMERSIVE_DARK_MODE = 20,         // [set] BOOL, Allows a window to either use the accent color, or dark, according to the user Color Mode preferences.
+   DWMWA_WINDOW_CORNER_PREFERENCE = 33,        // [set] WINDOW_CORNER_PREFERENCE, Controls the policy that rounds top-level window corners
+   DWMWA_BORDER_COLOR,                         // [set] COLORREF, The color of the thin border around a top-level window
+   DWMWA_CAPTION_COLOR,                        // [set] COLORREF, The color of the caption
+   DWMWA_TEXT_COLOR,                           // [set] COLORREF, The color of the caption text
+   DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,       // [get] UINT, width of the visible border around a thick frame window
    DWMWA_LAST
};

So the DWMWA_USE_IMMERSIVE_DARK_MODE and the DWMWA_CAPTION_COLOR are documented on Windows 11.
I dont really know what DWMWA_USE_HOSTBACKDROPBRUSH flag can do but maybe it's what MS Edge Dev uses for acrylic menus.
I did the same diff for uxtheme.h but I can't see any dark mode traces for the Win32 controls. Did you see anything about it? I had thought that the other undocumented APIs mentioned by @sylveon would have been made public for Windows 11.

@zodiacon
Copy link

zodiacon commented Aug 4, 2021

I am still hopeful :)

@riverar
Copy link
Contributor

riverar commented Aug 4, 2021

Please hold off on using these undocumented APIs/constants. There will be a supported path, stay tuned!

@sylveon
Copy link
Author

sylveon commented Aug 4, 2021

Those DWM flags don't seem undocumented if they're part of the 22000 SDK though (well, the actual docs are missing because they're still preview APIs, but they're clearly meant for public usage).

@mveril DWMWA_USE_HOSTBACKDROPBRUSH allows Win32 apps to build and use host backdrop brushes from Windows.UI.Composition. There's some info about it here.

@riverar
Copy link
Contributor

riverar commented Aug 4, 2021

Thanks for confirming they are not documented @sylveon. 😂

I'm just giving you a hard time, I know what you mean. Just trying to keep folks away from using the accent policy hacks. I believe there will be a few documented lines to solve all our Mica, corner, etc. needs.

@mveril
Copy link

mveril commented Aug 4, 2021

Ok @riverar maybe we are just too eager ! So wait and see…
For rounded corners however this is already documented

@lolametro
Copy link

Windows 11 is releasing in a few days, still no documented APIs. Any updates?

@AzAgarampur
Copy link

AzAgarampur commented Sep 28, 2021

I think that the dark mode api's from uxtheme.dll should also change the predefined system colors (GetSysColors()) to return their equivalent darkmode variants when dark mode is enabled. For example, COLOR_WINDOW+1 should represent a dark gray/black brush when darkmode is enabled, and return the standard white when light mode is being used.

@lb90
Copy link

lb90 commented Mar 19, 2023

Hi!

What in fact happens is that title bar is set to dark mode regardless of system settings

Yeah, true!

I don't need much, I just don't want to use undocumented API. I only need title bar color to follow system settings, but this is not the case.

No, you don't need undocumented API. Just use Windows.UI.ViewManagement.UISettings3::GetColorValues(). To be notified of changes, you can either create a hidden top-level window and listen for WM_SETTINGCHANGE or use WinRT API. I'll post here some code tomorrow 😉

@castorix
Copy link

Just use Windows.UI.ViewManagement.UISettings3::GetColorValues(). To be notified of changes, you can either create a hidden top-level window and listen for WM_SETTINGCHANGE or use WinRT API

You can handle WM_SETTINGCHANGE in the main window.
I had also posted a test code in C++ in this thread :
https://learn.microsoft.com/en-us/answers/questions/825610/windows-runtime-message-handler

@sylveon
Copy link
Author

sylveon commented Mar 19, 2023

Luminance calculations off UISettings is a workaround that got enshrined in docs, IMO. It is not a proper solution.

@kasper93
Copy link

Also don't forget the fact DWMWA_USE_IMMERSIVE_DARK_MODE is not doing what documentation says.

@pratikone
Copy link
Contributor

Closing as this is not an issue but more of a feature request (even when the scope is outside of Winappsdk.)

@pratikone pratikone closed this as not planned Won't fix, can't repro, duplicate, stale Oct 5, 2023
@sylveon
Copy link
Author

sylveon commented Oct 5, 2023

Where do feature requests go, if not GitHub issues?

@riverar
Copy link
Contributor

riverar commented Oct 5, 2023

Ouch, where do we go from here @pratikone? This is a dev fundamental that needs to get resolved.

@Markus-Siegel
Copy link

Markus-Siegel commented Oct 5, 2023

Sadly after years, Microsoft still didn't fix the contrast issues with dark mode inside the native save file dialog when using IFileDialogCustomize. I just tested with Notepad++ save file dialog.
IFileDialogCustomize is an offical API but no notice in the docs about it is broken when using the native Windows dark mode.
https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifiledialogcustomize
See my old post: #41 (comment)
Accessibility Insights for Windows reports this is failing all contrast tests.
I highly doubt this is not fixable for Windows. All native programs with the native save file dialog suffer from this. I'm wondering why this is still not fixed.
IFileDialogCustomize_dark_mode_contrast_issue
IFileDialogCustomize_dark_mode_contrast_issue2

@MattBDev
Copy link

MattBDev commented Oct 5, 2023

Is it fair to assume that from the closing of this issue that the WindowsAppSDK is no longer accepting feature requests?
I mean I know the right place for this feedback is not in the Feedback Hub so where else is this issue supposed to go?

@pratikone
Copy link
Contributor

pratikone commented Oct 5, 2023

My bad. I will re-open this so that all feedback gets captured and I can find someone who can get a new correct home for this issue. Being here, it can get lost against other issues. We can revisit closing the issue here (the one in winappsdk) later, once we have some clarity

@pratikone pratikone reopened this Oct 5, 2023
@Poopooracoocoo

This comment was marked as off-topic.

@driver1998
Copy link

WinForms are looking to provide dark mode support, but that would require the dark mode common controls mentioned here.

Otherwise it would be impossible unless WinForms owner draw all of its controls, which kind of defeats its purpose.

@Chaoses-Ib
Copy link

ShouldAppsUseDarkMode may return wrong values on some systems. For example, on my Windows 11 23H2 (22631.3085), it will always return true regardless of what the app mode is:

image

Some people have found it wrong too, though some also found it correct: ysc3839/win32-darkmode#3. I'm not sure what's the reason. HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme is always correct.

@Markus-Siegel
Copy link

Current dark mode API state is still NOT satisfying.
In order to enable proper dark mode for my Win32 application, I need to do several weird things.

1.) To enable dark mode file explorer dialog I need to call SetWindowTheme(hWnd, L"DarkMode_Explorer", nullptr); manually. Fun fact, this also enables dark mode for SOME Common Controls but not for all. Thanks god I not relying on common controls anymore but for legacy application wanting to enhance their theming system, this really sucks.

2.) To enable the dark mode win32 context menu for the titlebar I need to call SetPreferredAppMode from uxtheme.dll which is private and undocumented for some reason. But it worked for me quite a time. If it is stable since years, why not finally document it? Why does DWMWINDOWATTRIBUTE::DWMWA_USE_IMMERSIVE_DARK_MODE set the titlebar color to dark but keeps the context menu still white? Who wants a dark mode titlebar with a white context menu? This created an inconsistent situation where some apps have a dark mode titlebar but still the white system context menu (like Notepad and Paint) but some apps like File Explorer and System Settings have the dark system context menu.

forgotten_dark_mode
The funny thing is even WinUI3 which was proclaimed as the newest and shiniest UI framework from Microsoft lacks the dark mode win32 context menu for some reason. Perhaps the other departments at Microsoft do not dare to use the undocumented uxtheme APIs or it was somehow forgotten, which I doubt because some other Microsoft apps did it right?

And these two steps are not documented anywhere in the developer docs. Only thanks to some guidance by other developers sharing their reverse engineering people wheere able to make their apps look a bit more professional in dark mode.

What developers need is really one simple API to enable dark mode which would affect everything (dark mode file explorer dialogs, dark mode titlebar, dark mode win32 and common controls). And please give us a stable API to detect dark mode other than registry key or color checks or undocumented uxtheme functions.

@sylveon
Copy link
Author

sylveon commented Mar 17, 2024

DarkMode_Explorer is also undocumented - all of this is undocumented. As it stands, Win32 cannot be made dark mode with documented ways.

This issue will probably go nowhere, because while it seemed like the initial goal of Project Reunion could've helped this, it now seems apparent that WASDK is just support for WinUI 3, and has little interest for any classic Win32 app.

@riverar
Copy link
Contributor

riverar commented Mar 17, 2024

Microsoft claims they'll have documentation on developing modern, fast, and reliable Windows apps available by BUILD timeframe https://twitter.com/WindowsDocs/status/1767636607195385951.

Something to keep an eye on, but don't hold your breath.

@sylveon
Copy link
Author

sylveon commented Mar 17, 2024

Hell, PowerToys and Accessibility Insights are now also reading the registry to detect dark mode, because the platform cannot give us a satisfying API for this:

https://github.com/microsoft/PowerToys/blob/f5797a065a5c2c448fd2b1780bd1353d712103c3/src/common/Themes/theme_helpers.cpp#L17-L24
https://github.com/microsoft/accessibility-insights-windows/blob/43db171a71b8855154edf5dc0d286d97c9de590f/src/AccessibilityInsights.Win32/Win32Helper.cs#L122-L130

And WPF soon will too:
https://github.com/dotnet/wpf/pull/8870/files#diff-19c2bb4471f21681a6403e1961acfcdceeec995ed16c9f936ba447848e020f9bR181-R184

I guess reading registry will be a supported way going forward if that's any indication 🙄

@sylveon
Copy link
Author

sylveon commented Mar 17, 2024

So will WinForms too, apparently: dotnet/winforms#7641 (comment)

Very disappointing that a proper API has not been added for this.

@YourOrdinaryCat
Copy link

Just realized, WPF will also fallback to SystemUsesLightTheme: https://github.com/dotnet/wpf/pull/8870/files#r1525179447

I suppose this means ShouldSystemUseDarkMode will never be available for proper use either. At least it should work downlevel (ShouldSystemUseDarkMode is 1903+), so I guess I'll be reading the registry to make my tray icon a different color...

@AraHaan
Copy link

AraHaan commented Mar 30, 2024

Just realized, WPF will also fallback to SystemUsesLightTheme: https://github.com/dotnet/wpf/pull/8870/files#r1525179447

I suppose this means ShouldSystemUseDarkMode will never be available for proper use either. At least it should work downlevel (ShouldSystemUseDarkMode is 1903+), so I guess I'll be reading the registry to make my tray icon a different color...

Sometimes the apis are bugged so registry is sadly the best way.

@castorix
Copy link

Someone told me in this thread that the registry key may not be present :
https://learn.microsoft.com/en-us/answers/questions/1161597/how-to-detect-windows-application-dark-mode

@YourOrdinaryCat
Copy link

Someone told me in this thread that the registry key may not be present

Yes, can confirm the above. On a clean 1809 install (which I made to test this particular thing), the only keys under HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize are ColorPrevalence and EnableTransparency. Is there any documentation on the default mode for apps & system when the key isn't there?

@sylveon
Copy link
Author

sylveon commented Mar 31, 2024

No, these keys are undocumented. And the defaults have varied depending on the OS version.

This is why it's important we have a proper API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests