MS Windows dark mode

350 views
Skip to first unread message

Manolo

unread,
Nov 15, 2022, 7:42:59 PM11/15/22
to wx-...@googlegroups.com
Hi
I'm fully motivated to find a way of setting dark mode for Win32 apps.
I don't think MS some day changes its dll's to allow this mode. Perhaps for
apps in Windows 11 or 12, but not now.

My approach (which I wish to discuss here) is an "invert and blit" solution.
I need:

1. Detect when a window is changed. Perhaps handling the UpdateUI or
Paint or
NotClient Paint or some menu event.
I don't know if this can be done without the user having to handle an event.

2. Store the window-ids, to apply dark mode only to those who have changed.
The user can set a flag for each window to apply this "theme". This will
allow to skip images or already customized windows.

3. Store the regions changed. Or keep a copy of the last version so as to
compare with new, and apply dark mode oly to changed pixels.

4. Call a function that runs only after all redraw is complete. Perhaps
using
OnIdle or CallAfter, or inspecting the event queue.

5. Use a change-mode function. A simple "invert" like newREDc = 255 -
oldREDc
may be enough. More complex would be an arc-function instead of a linear
one.
Even we could allow using an array[256] such that newREDc = array[oldREDc]
Funny effects will happen, like pure red (255,0,0) becomes light green-blue
(0,255,255), but worst is nothing.

6. Copy the window content to a bitmap/image. Apply the function.

7. For each window affected, Blit the new bitmap to the window. Better
avoiding
a new paint-event, or set a flag so as to avoid infinite redraws.


What do you think?


Regards,
Manolo


PB

unread,
Nov 16, 2022, 1:36:07 AM11/16/22
to wx-dev
My two cents.

I may be wrong, but I cannot imagine your solution to be anywhere near glitchless. Moreover, wxWindowDC/wxClientDC are unusable with double buffering on.

I think it is needed to use whatever little MS provided, e.g., do what Notepad++ does (NPP also supports theming behind light/dark mode) Win32 API-wise and add wxWidgets stuff such as fake the results of wxSystemSettings::GetColour() when in the dark mode. The problem is that some controls require extra work to appear correctly, it is not as simple as to just switch a control to the dark theme. It appears more hacks are needed for menus or scrollbars. I am not even sure all the common controls and dialogs support the dark mode properly.

Last but not least, the dark mode API is undocumented and hence such code may not get accepted into wxWidgets code base.

I myself did a research into this,  wrote some wxWidgets code for it but nothing important or publishable. OTOH, to have something up and running should not take long, it's probably the last 5% to get (close to) perfect that would take most of the time.

Regards,
PB





Vadim Zeitlin

unread,
Nov 16, 2022, 8:41:17 AM11/16/22
to wx-...@googlegroups.com
On Wed, 16 Nov 2022 01:42:56 +0100 Manolo wrote:

M> I'm fully motivated to find a way of setting dark mode for Win32 apps.

Hello,

FWIW I've just resumed working on my msw-dark-mode branch last weekend,
after a long hiatus (I started back in June, I think). The reason for this
long delay is that the progress is very slow because there are a lot of
problems and I spent a lot of time trying to find solutions only to
discover, or confirm, that there doesn't seem any. It's all rather
discouraging, but we have to do one way or the other because it's clear
that many people/projects require this. In fact, in the interest of full
disclosure, my own work on this is partially sponsored by KiCad project.

M> My approach (which I wish to discuss here) is an "invert and blit" solution.

Sorry, I don't think this is really viable, as PB already wrote, you can't
use wxClientDC when using double buffering (WS_EX_COMPOSITED) at all. Maybe
we could use WM_PRINTCLIENT to draw the native control on a memory HDC in
WM_PAINT, but I haven't actually tried doing it yet.

What I did try was to use the undocumented SetPreferredAppMode() and
AllowDarkModeForWindow() functions from uxtheme.dll and then setting the
window theme to "Explorer", which is the same approach as Notepad++ and
other open source windows applications use for dark mode support.

Unfortunately, as PB also wrote, this is not nearly enough. Many controls
don't draw correctly in the dark mode, LISTVIEW is particularly bad (I
spent literally half a day on trying to make it work without custom-drawing
everything, but still failed), but it's far from being the only one. Most
(all?) standard dialogs are broken, in particular I couldn't find any way
at all to make task dialog (used by wxMessageBox()) use the dark mode. The
only solution seems to be to owner-draw the controls that don't support
dark mode [well], e.g. wxSpinButton, and not use the dialogs that don't
support it, which is hardly ideal, but there just doesn't seem to be
anything else to do.

I can push the branch with what I already have so far, which is admittedly
not much, if anybody is interested in working on this. If not, I'd rather
delay it a bit more until I can fix at least the most glaring problems. But
I do hope to make something public relatively soon, see the beginning of
this email.

Regards,
VZ

Manolo

unread,
Nov 16, 2022, 1:32:49 PM11/16/22
to wx-...@googlegroups.com
Thanks for your comments.

I'm not trying to replace OS drawing. This is not possible except for
controls allowing ownerdraw, or at least allowing back/foreground colors
(button, textctrl, scintilla,...)
Vadim, you're going very deep in your try. But I think your only option,
perhaps, is some sort of hacking by dll-injection.

What I'm speaking of is to store regions to update. In a paint-event
this is easy. More tricky in a UpdateUI event I have to investigate for
menus, scrollbars, common dialogs, and some more.

After everything is drawn I get a window shot, apply the proper color
function, and blit the new images back to the window.
If this can't be done because of double-buffering, then I have to do a
screenshot and retrieve the window region I have stored previously.

I guess the result won't be entirely satisfactory. And flicker is
threatening. But I think it's feasible, and better than no dark-mode at all.

Regards,
Manolo


Vadim Zeitlin

unread,
Nov 16, 2022, 1:54:08 PM11/16/22
to wx-...@googlegroups.com
On Wed, 16 Nov 2022 19:32:45 +0100 Manolo wrote:

M> I'm not trying to replace OS drawing.

Sorry, but what are you trying to do then? AFAICS it should be already
possible to paint your own windows using the correct (for dark mode)
colours. We have wxSystemSettings::GetAppearance() and my branch changes
GetColour() to return more appropriate colours in dark mode, so you can
just use this.

The problem right now is that all standard UI elements, including all
standard controls (buttons, text, ...), menus, scrollbars etc don't adapt
to the dark mode. This is what I'm trying to change.

Regards,
VZ

Manolo

unread,
Nov 16, 2022, 2:03:17 PM11/16/22
to wx-...@googlegroups.com


> On Wed, 16 Nov 2022 19:32:45 +0100 Manolo wrote:
>
> M> I'm not trying to replace OS drawing.
>
> Sorry, but what are you trying to do then?

Call me idiot or rough. My method looks like this:

+ Using your phone, take a picture of the screen.
+ Download the image to your PC and edit it with some program.
+ Replace the whole screen with the edited image.

Manolo

Vadim Zeitlin

unread,
Nov 16, 2022, 2:07:03 PM11/16/22
to wx-...@googlegroups.com
On Wed, 16 Nov 2022 20:03:15 +0100 Manolo wrote:

M> > On Wed, 16 Nov 2022 19:32:45 +0100 Manolo wrote:
M> >
M> > M> I'm not trying to replace OS drawing.
M> >
M> > Sorry, but what are you trying to do then?
M>
M> Call me idiot or rough. My method looks like this:
M>
M> + Using your phone, take a picture of the screen.
M> + Download the image to your PC and edit it with some program.
M> + Replace the whole screen with the edited image.

Again, I'm pretty sure this can't work with WS_EX_COMPOSITED as the
compositor must have the actual bitmap representing the window contents in
this case. And if you turn it off you will certainly have horrible flicker
on every resize and scroll operation.

In short, I really don't think this can work, but please feel free to
prove me wrong.

Regards,
VZ

PB

unread,
Nov 16, 2022, 2:43:59 PM11/16/22
to wx-dev
FWIW, I have just now rechecked the issues I reported with File Explorer in dark mode with older Windows 10 versions here with version 22H2 of both Windows 10 and 11. Unfortunately, there is no change and even Microsoft's very own prominent application still has many problems, some mentioned by vadz (listview, taskdialog).

The rest of bundled Win32 applications (checked Notepad, Wordpad, Task Manager, Registry Editor) still have no dark mode support at all, except that Notepad and Task Manager on Windows 11 support dark mode but no longer look like Win32 apps.

I think the message is pretty clear and  Win32 is getting nothing else here in  by Microsoft...

Regards,
PB

Manolo

unread,
Feb 22, 2023, 4:16:30 PM2/22/23
to wx-...@googlegroups.com
I wanted to test my approach. So, I implemented an "inverse-drawing"
that is called at wxApp:OnIdle. It takes the whole screen and works only
with app's TLWs areas, also discarding unchanged pixels.

I want to share my findings, for anyone trying to do the same as me.

1) Flicker is present, even I managed to reduce it. It resulted in a
good way to detect how many times a control is drawn. For example for a
simple mouse hover it, a text-ctrl may flick about 3 to 5 times (I
expected just 2).

2) Due to working at OnIdle, if the user has fast activity (scrolling,
dragging, etc) the "normal" view is used. Only the inverse process is
called when mouse is released and then OnIdle is available.

3) Native dialogs are not handled, even if they lay inside a TLW, by the
simple fact that usually they are modal, so the app will not receive
idle event.

4) The worst is the text. You may think a normal text is black, just
black. But if you open NotePad, make a screenshot, paste it in your
favorite drawing app (MSW Paint is enough), and set full zoom, you'll
see how Windows uses a combination of different colours. So, inverting
the text makes an unreadable effect, no matter what formula you use in
the invert-function. Same goes for white text.

While some of the above may be somehow improved, the text inversion is
not aceptable.
Conclusion: Do not follow this approach.

Regards,
Manolo


Reply all
Reply to author
Forward
0 new messages