Start adding dark mode support to wxMSW

Add experimental wxApp::MSWEnableDarkMode().

For now this is very incomplete and just uses the basic API for setting
the preferred application mode, which only works for the standard
dialogs and popup menus, but it's a start.

Support of dark mode is also limited to Windows 10 20H1 and later, but
this should, hopefully, be not a problem in practice as few people
should be using pre-2020 Windows 10 versions by now.
This commit is contained in:
Vadim Zeitlin 2022-06-24 18:42:52 +01:00
parent 6e940d1a10
commit bcea384923
11 changed files with 236 additions and 0 deletions

View file

@ -5291,6 +5291,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS = \
monodll_timectrl.o \
monodll_datecontrols.o \
monodll_generic_activityindicator.o \
monodll_darkmode.o \
monodll_msw_checklst.o \
monodll_msw_fdrepdlg.o \
monodll_msw_fontdlg.o
@ -7046,6 +7047,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_1 = \
monolib_timectrl.o \
monolib_datecontrols.o \
monolib_generic_activityindicator.o \
monolib_darkmode.o \
monolib_msw_checklst.o \
monolib_msw_fdrepdlg.o \
monolib_msw_fontdlg.o
@ -8955,6 +8957,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_2 = \
coredll_timectrl.o \
coredll_datecontrols.o \
coredll_generic_activityindicator.o \
coredll_darkmode.o \
coredll_msw_checklst.o \
coredll_msw_fdrepdlg.o \
coredll_msw_fontdlg.o
@ -10440,6 +10443,7 @@ COND_TOOLKIT_MSW___GUI_SRC_OBJECTS_3 = \
corelib_timectrl.o \
corelib_datecontrols.o \
corelib_generic_activityindicator.o \
corelib_darkmode.o \
corelib_msw_checklst.o \
corelib_msw_fdrepdlg.o \
corelib_msw_fontdlg.o
@ -15253,6 +15257,9 @@ monodll_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(MONODLL_ODEP)
monodll_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(MONODLL_ODEP)
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
monodll_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(MONODLL_ODEP)
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
monodll_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(MONODLL_ODEP)
$(CXXC) -c -o $@ $(MONODLL_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
@ -20017,6 +20024,9 @@ monolib_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(MONOLIB_ODEP)
monolib_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(MONOLIB_ODEP)
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
monolib_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(MONOLIB_ODEP)
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
monolib_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(MONOLIB_ODEP)
$(CXXC) -c -o $@ $(MONOLIB_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
@ -25465,6 +25475,9 @@ coredll_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(COREDLL_ODEP)
coredll_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(COREDLL_ODEP)
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
coredll_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(COREDLL_ODEP)
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
coredll_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(COREDLL_ODEP)
$(CXXC) -c -o $@ $(COREDLL_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp
@ -29197,6 +29210,9 @@ corelib_timectrl.o: $(srcdir)/src/msw/timectrl.cpp $(CORELIB_ODEP)
corelib_datecontrols.o: $(srcdir)/src/msw/datecontrols.cpp $(CORELIB_ODEP)
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/datecontrols.cpp
corelib_darkmode.o: $(srcdir)/src/msw/darkmode.cpp $(CORELIB_ODEP)
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/darkmode.cpp
corelib_msw_checklst.o: $(srcdir)/src/msw/checklst.cpp $(CORELIB_ODEP)
$(CXXC) -c -o $@ $(CORELIB_CXXFLAGS) $(srcdir)/src/msw/checklst.cpp

View file

@ -1847,6 +1847,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
src/msw/timectrl.cpp
src/msw/datecontrols.cpp
src/generic/activityindicator.cpp
src/msw/darkmode.cpp
</set>
<set var="MSW_HDR" hints="files">
wx/generic/clrpickerg.h

View file

@ -1736,6 +1736,7 @@ set(MSW_SRC
src/msw/datetimectrl.cpp
src/msw/hyperlink.cpp
src/generic/activityindicator.cpp
src/msw/darkmode.cpp
)
set(MSW_HDR

View file

@ -1685,6 +1685,7 @@ MSW_SRC =
src/msw/commandlinkbutton.cpp
src/msw/control.cpp
src/msw/customdraw.cpp
src/msw/darkmode.cpp
src/msw/datecontrols.cpp
src/msw/datectrl.cpp
src/msw/datetimectrl.cpp

View file

@ -1997,6 +1997,7 @@ ____CORE_SRC_FILENAMES_OBJECTS = \
$(OBJS)\monodll_timectrl.o \
$(OBJS)\monodll_datecontrols.o \
$(OBJS)\monodll_activityindicator.o \
$(OBJS)\monodll_darkmode.o \
$(OBJS)\monodll_msw_checklst.o \
$(OBJS)\monodll_msw_fdrepdlg.o \
$(OBJS)\monodll_fontdlg.o \
@ -2845,6 +2846,7 @@ ____CORE_SRC_FILENAMES_1_OBJECTS = \
$(OBJS)\monolib_timectrl.o \
$(OBJS)\monolib_datecontrols.o \
$(OBJS)\monolib_activityindicator.o \
$(OBJS)\monolib_darkmode.o \
$(OBJS)\monolib_msw_checklst.o \
$(OBJS)\monolib_msw_fdrepdlg.o \
$(OBJS)\monolib_fontdlg.o \
@ -3575,6 +3577,7 @@ ____CORE_SRC_FILENAMES_2_OBJECTS = \
$(OBJS)\coredll_timectrl.o \
$(OBJS)\coredll_datecontrols.o \
$(OBJS)\coredll_activityindicator.o \
$(OBJS)\coredll_darkmode.o \
$(OBJS)\coredll_msw_checklst.o \
$(OBJS)\coredll_msw_fdrepdlg.o \
$(OBJS)\coredll_fontdlg.o \
@ -4262,6 +4265,7 @@ ____CORE_SRC_FILENAMES_3_OBJECTS = \
$(OBJS)\corelib_timectrl.o \
$(OBJS)\corelib_datecontrols.o \
$(OBJS)\corelib_activityindicator.o \
$(OBJS)\corelib_darkmode.o \
$(OBJS)\corelib_msw_checklst.o \
$(OBJS)\corelib_msw_fdrepdlg.o \
$(OBJS)\corelib_fontdlg.o \
@ -7507,6 +7511,9 @@ $(OBJS)\monodll_timectrl.o: ../../src/msw/timectrl.cpp
$(OBJS)\monodll_datecontrols.o: ../../src/msw/datecontrols.cpp
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\monodll_darkmode.o: ../../src/msw/darkmode.cpp
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\monodll_msw_checklst.o: ../../src/msw/checklst.cpp
$(CXX) -c -o $@ $(MONODLL_CXXFLAGS) $(CPPDEPS) $<
@ -10094,6 +10101,9 @@ $(OBJS)\monolib_timectrl.o: ../../src/msw/timectrl.cpp
$(OBJS)\monolib_datecontrols.o: ../../src/msw/datecontrols.cpp
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\monolib_darkmode.o: ../../src/msw/darkmode.cpp
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\monolib_msw_checklst.o: ../../src/msw/checklst.cpp
$(CXX) -c -o $@ $(MONOLIB_CXXFLAGS) $(CPPDEPS) $<
@ -13107,6 +13117,9 @@ $(OBJS)\coredll_timectrl.o: ../../src/msw/timectrl.cpp
$(OBJS)\coredll_datecontrols.o: ../../src/msw/datecontrols.cpp
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\coredll_darkmode.o: ../../src/msw/darkmode.cpp
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\coredll_msw_checklst.o: ../../src/msw/checklst.cpp
$(CXX) -c -o $@ $(COREDLL_CXXFLAGS) $(CPPDEPS) $<
@ -14857,6 +14870,9 @@ $(OBJS)\corelib_timectrl.o: ../../src/msw/timectrl.cpp
$(OBJS)\corelib_datecontrols.o: ../../src/msw/datecontrols.cpp
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\corelib_darkmode.o: ../../src/msw/darkmode.cpp
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<
$(OBJS)\corelib_msw_checklst.o: ../../src/msw/checklst.cpp
$(CXX) -c -o $@ $(CORELIB_CXXFLAGS) $(CPPDEPS) $<

View file

@ -2314,6 +2314,7 @@ ____CORE_SRC_FILENAMES_OBJECTS = \
$(OBJS)\monodll_timectrl.obj \
$(OBJS)\monodll_datecontrols.obj \
$(OBJS)\monodll_activityindicator.obj \
$(OBJS)\monodll_darkmode.obj \
$(OBJS)\monodll_msw_checklst.obj \
$(OBJS)\monodll_msw_fdrepdlg.obj \
$(OBJS)\monodll_fontdlg.obj \
@ -3162,6 +3163,7 @@ ____CORE_SRC_FILENAMES_1_OBJECTS = \
$(OBJS)\monolib_timectrl.obj \
$(OBJS)\monolib_datecontrols.obj \
$(OBJS)\monolib_activityindicator.obj \
$(OBJS)\monolib_darkmode.obj \
$(OBJS)\monolib_msw_checklst.obj \
$(OBJS)\monolib_msw_fdrepdlg.obj \
$(OBJS)\monolib_fontdlg.obj \
@ -3942,6 +3944,7 @@ ____CORE_SRC_FILENAMES_2_OBJECTS = \
$(OBJS)\coredll_timectrl.obj \
$(OBJS)\coredll_datecontrols.obj \
$(OBJS)\coredll_activityindicator.obj \
$(OBJS)\coredll_darkmode.obj \
$(OBJS)\coredll_msw_checklst.obj \
$(OBJS)\coredll_msw_fdrepdlg.obj \
$(OBJS)\coredll_fontdlg.obj \
@ -4627,6 +4630,7 @@ ____CORE_SRC_FILENAMES_3_OBJECTS = \
$(OBJS)\corelib_timectrl.obj \
$(OBJS)\corelib_datecontrols.obj \
$(OBJS)\corelib_activityindicator.obj \
$(OBJS)\corelib_darkmode.obj \
$(OBJS)\corelib_msw_checklst.obj \
$(OBJS)\corelib_msw_fdrepdlg.obj \
$(OBJS)\corelib_fontdlg.obj \
@ -7952,6 +7956,9 @@ $(OBJS)\monodll_timectrl.obj: ..\..\src\msw\timectrl.cpp
$(OBJS)\monodll_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
$(OBJS)\monodll_darkmode.obj: ..\..\src\msw\darkmode.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\darkmode.cpp
$(OBJS)\monodll_msw_checklst.obj: ..\..\src\msw\checklst.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONODLL_CXXFLAGS) ..\..\src\msw\checklst.cpp
@ -10539,6 +10546,9 @@ $(OBJS)\monolib_timectrl.obj: ..\..\src\msw\timectrl.cpp
$(OBJS)\monolib_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
$(OBJS)\monolib_darkmode.obj: ..\..\src\msw\darkmode.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\darkmode.cpp
$(OBJS)\monolib_msw_checklst.obj: ..\..\src\msw\checklst.cpp
$(CXX) /c /nologo /TP /Fo$@ $(MONOLIB_CXXFLAGS) ..\..\src\msw\checklst.cpp
@ -13552,6 +13562,9 @@ $(OBJS)\coredll_timectrl.obj: ..\..\src\msw\timectrl.cpp
$(OBJS)\coredll_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
$(OBJS)\coredll_darkmode.obj: ..\..\src\msw\darkmode.cpp
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\darkmode.cpp
$(OBJS)\coredll_msw_checklst.obj: ..\..\src\msw\checklst.cpp
$(CXX) /c /nologo /TP /Fo$@ $(COREDLL_CXXFLAGS) ..\..\src\msw\checklst.cpp
@ -15302,6 +15315,9 @@ $(OBJS)\corelib_timectrl.obj: ..\..\src\msw\timectrl.cpp
$(OBJS)\corelib_datecontrols.obj: ..\..\src\msw\datecontrols.cpp
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\datecontrols.cpp
$(OBJS)\corelib_darkmode.obj: ..\..\src\msw\darkmode.cpp
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\darkmode.cpp
$(OBJS)\corelib_msw_checklst.obj: ..\..\src\msw\checklst.cpp
$(CXX) /c /nologo /TP /Fo$@ $(CORELIB_CXXFLAGS) ..\..\src\msw\checklst.cpp

View file

@ -613,6 +613,7 @@
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)msw_%(Filename).obj</ObjectFileName>
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='DLL Release|x64'">$(IntDir)msw_%(Filename).obj</ObjectFileName>
</ClCompile>
<ClCompile Include="..\..\src\msw\darkmode.cpp" />
<ClCompile Include="..\..\src\msw\graphicsd2d.cpp" />
<ClCompile Include="..\..\src\msw\ole\access.cpp" />
<ClCompile Include="..\..\src\msw\ole\activex.cpp" />

View file

@ -1074,6 +1074,9 @@
<ClCompile Include="..\..\src\xrc\xmlreshandler.cpp">
<Filter>Common Sources</Filter>
</ClCompile>
<ClCompile Include="..\..\src\msw\darkmode.cpp">
<Filter>MSW Sources</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\src\msw\version.rc">

View file

@ -37,6 +37,14 @@ public:
virtual void SetPrintMode(int mode) override { m_printMode = mode; }
virtual int GetPrintMode() const { return m_printMode; }
// MSW-specific function to enable experimental dark mode support.
enum
{
DarkMode_Auto = 0, // Use dark mode if the system is using it.
DarkMode_Always = 1 // Force using dark mode.
};
bool MSWEnableDarkMode(int flags = 0);
// implementation only
void OnIdle(wxIdleEvent& event);
void OnEndSession(wxCloseEvent& event);

View file

@ -1181,6 +1181,33 @@ public:
///@}
/**
@name MSW-specific functions
*/
//@{
/**
Enable experimental dark mode support for MSW applications.
This function uses @e undocumented, and unsupported by Microsoft,
functions to enable dark mode support for the desktop applications
under Windows 10 20H1 or later (including all Windows 11 versions).
@param flags Can include @c wxApp::DarkMode_Always to force enabling
dark mode for the application, even if the system doesn't use the
dark mode by default. Otherwise dark mode is only used if it is the
default mode for the applications on the current system.
@return @true if dark mode support was enabled, @false if it couldn't
be done, most likely because the system doesn't support dark mode.
@onlyfor{wxmsw}
@since 3.3.0
*/
bool MSWEnableDarkMode(int flags = 0);
//@}
};

146
src/msw/darkmode.cpp Normal file
View file

@ -0,0 +1,146 @@
///////////////////////////////////////////////////////////////////////////////
// Name: src/msw/darkmode.cpp
// Purpose: Support for dark mode in wxMSW
// Author: Vadim Zeitlin
// Created: 2022-06-24
// Copyright: (c) 2022 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
/*
The code in this file is based on the following sources:
- win32-darkmode by Richard Yu (https://github.com/ysc3839/win32-darkmode)
*/
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
// Allow predefining this as 0 to disable dark mode support completely.
#ifndef wxUSE_DARK_MODE
// Otherwise enable it by default.
#define wxUSE_DARK_MODE 1
#endif
#if wxUSE_DARK_MODE
#ifndef WX_PRECOMP
#include "wx/app.h"
#include "wx/log.h"
#endif // WX_PRECOMP
#include "wx/dynlib.h"
static const char* TRACE_DARKMODE = "msw-darkmode";
namespace
{
// Constants for use with SetPreferredAppMode().
enum PreferredAppMode
{
AppMode_Default,
AppMode_AllowDark,
AppMode_ForceDark,
AppMode_ForceLight
};
PreferredAppMode gs_appMode = AppMode_Default;
template <typename T>
bool TryLoadByOrd(T& func, const wxDynamicLibrary& lib, int ordinal)
{
func = (T)::GetProcAddress(lib.GetLibHandle(), MAKEINTRESOURCEA(ordinal));
if ( !func )
{
wxLogTrace(TRACE_DARKMODE,
"Required function with ordinal %d not found", ordinal);
return false;
}
return true;
}
} // anonymous namespace
// ============================================================================
// implementation
// ============================================================================
namespace wxMSWImpl
{
// Global pointers of the functions we use: they're not only undocumented, but
// don't appear in the SDK headers at all.
BOOL (WINAPI *ShouldAppsUseDarkMode)() = nullptr;
DWORD (WINAPI *SetPreferredAppMode)(DWORD) = nullptr;
bool InitDarkMode()
{
// Note: for simplicity, we support dark mode only in Windows 10 v2004
// ("20H1", build number 19041) and later, even if, in principle, it could
// be supported as far back as v1809 (build 17763) -- but very few people
// must still use it by now and so it just doesn't seem to be worth it.
if ( !wxCheckOsVersion(10, 0, 19041) )
{
wxLogTrace(TRACE_DARKMODE, "Unsupported due to OS version");
return false;
}
wxLoadedDLL dllUxTheme(wxS("uxtheme.dll"));
// These functions are not only undocumented but are not even exported by
// name, and have to be resolved using their ordinals.
if ( !TryLoadByOrd(ShouldAppsUseDarkMode, dllUxTheme, 132) )
return false;
if ( !TryLoadByOrd(SetPreferredAppMode, dllUxTheme, 135) )
return false;
return true;
}
} // namespace wxMSWImpl
// ----------------------------------------------------------------------------
// Public API
// ----------------------------------------------------------------------------
bool wxApp::MSWEnableDarkMode(int flags)
{
if ( !wxMSWImpl::InitDarkMode() )
return false;
const PreferredAppMode mode = flags & DarkMode_Always ? AppMode_ForceDark
: AppMode_AllowDark;
const DWORD rc = wxMSWImpl::SetPreferredAppMode(mode);
// It's supposed to return the old mode normally.
if ( rc != static_cast<DWORD>(gs_appMode) )
{
wxLogTrace(TRACE_DARKMODE,
"SetPreferredAppMode(%d) unexpectedly returned %d",
mode, rc);
}
gs_appMode = mode;
return true;
}
#else // !wxUSE_DARK_MODE
bool wxApp::MSWEnableDarkMode(int WXUNUSED(flags))
{
return false;
}
#endif // wxUSE_DARK_MODE/!wxUSE_DARK_MODE