Skip to content

Dark Mode changes to official API integration #70

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
_build/
ARM64/
Win32/
x64/
*.d
*.o
*.user
NppSnippets.dll
version_git.h
plugindemo-2025_May_21.10.1/.gitignore
.claude/settings.local.json
.claude/todo.txt
.gitignore
NppSnippets/
338 changes: 338 additions & 0 deletions DarkMode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
#include <windows.h>

#include "DarkMode.h"
#include "IatHook.h"

#include <uxtheme.h>
#include <vssym32.h>

#include <unordered_set>
#include <mutex>

#if defined(__GNUC__) && __GNUC__ > 8
#define WINAPI_LAMBDA_RETURN(return_t) -> return_t WINAPI
#elif defined(__GNUC__)
#define WINAPI_LAMBDA_RETURN(return_t) WINAPI -> return_t
#else
#define WINAPI_LAMBDA_RETURN(return_t) -> return_t
#endif

enum IMMERSIVE_HC_CACHE_MODE
{
IHCM_USE_CACHED_VALUE,
IHCM_REFRESH
};

// 1903 18362
enum class PreferredAppMode
{
Default,
AllowDark,
ForceDark,
ForceLight,
Max
};

enum WINDOWCOMPOSITIONATTRIB
{
WCA_UNDEFINED = 0,
WCA_NCRENDERING_ENABLED = 1,
WCA_NCRENDERING_POLICY = 2,
WCA_TRANSITIONS_FORCEDISABLED = 3,
WCA_ALLOW_NCPAINT = 4,
WCA_CAPTION_BUTTON_BOUNDS = 5,
WCA_NONCLIENT_RTL_LAYOUT = 6,
WCA_FORCE_ICONIC_REPRESENTATION = 7,
WCA_EXTENDED_FRAME_BOUNDS = 8,
WCA_HAS_ICONIC_BITMAP = 9,
WCA_THEME_ATTRIBUTES = 10,
WCA_NCRENDERING_EXILED = 11,
WCA_NCADORNMENTINFO = 12,
WCA_EXCLUDED_FROM_LIVEPREVIEW = 13,
WCA_VIDEO_OVERLAY_ACTIVE = 14,
WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15,
WCA_DISALLOW_PEEK = 16,
WCA_CLOAK = 17,
WCA_CLOAKED = 18,
WCA_ACCENT_POLICY = 19,
WCA_FREEZE_REPRESENTATION = 20,
WCA_EVER_UNCLOAKED = 21,
WCA_VISUAL_OWNER = 22,
WCA_HOLOGRAPHIC = 23,
WCA_EXCLUDED_FROM_DDA = 24,
WCA_PASSIVEUPDATEMODE = 25,
WCA_USEDARKMODECOLORS = 26,
WCA_LAST = 27
};

struct WINDOWCOMPOSITIONATTRIBDATA
{
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};

using fnRtlGetNtVersionNumbers = void (WINAPI *)(LPDWORD major, LPDWORD minor, LPDWORD build);
using fnSetWindowCompositionAttribute = BOOL (WINAPI *)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*);
using fnShouldAppsUseDarkMode = bool (WINAPI *)(); // ordinal 132
using fnAllowDarkModeForWindow = bool (WINAPI *)(HWND hWnd, bool allow); // ordinal 133
using fnAllowDarkModeForApp = bool (WINAPI *)(bool allow); // ordinal 135, in 1809
using fnFlushMenuThemes = void (WINAPI *)(); // ordinal 136
using fnRefreshImmersiveColorPolicyState = void (WINAPI *)(); // ordinal 104
using fnIsDarkModeAllowedForWindow = bool (WINAPI *)(HWND hWnd); // ordinal 137
using fnGetIsImmersiveColorUsingHighContrast = bool (WINAPI *)(IMMERSIVE_HC_CACHE_MODE mode); // ordinal 106
using fnOpenNcThemeData = HTHEME(WINAPI *)(HWND hWnd, LPCWSTR pszClassList); // ordinal 49
using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode); // ordinal 135, in 1903

fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr;
fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = nullptr;
fnAllowDarkModeForWindow _AllowDarkModeForWindow = nullptr;
fnAllowDarkModeForApp _AllowDarkModeForApp = nullptr;
fnFlushMenuThemes _FlushMenuThemes = nullptr;
fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = nullptr;
fnIsDarkModeAllowedForWindow _IsDarkModeAllowedForWindow = nullptr;
fnGetIsImmersiveColorUsingHighContrast _GetIsImmersiveColorUsingHighContrast = nullptr;
fnOpenNcThemeData _OpenNcThemeData = nullptr;
fnSetPreferredAppMode _SetPreferredAppMode = nullptr;

bool g_darkModeSupported = false;
bool g_darkModeEnabled = false;
DWORD g_buildNumber = 0;

bool ShouldAppsUseDarkMode()
{
if (!_ShouldAppsUseDarkMode)
{
return false;
}

return _ShouldAppsUseDarkMode();
}

bool AllowDarkModeForWindow(HWND hWnd, bool allow)
{
if (g_darkModeSupported && _AllowDarkModeForWindow)
return _AllowDarkModeForWindow(hWnd, allow);
return false;
}

bool IsHighContrast()
{
HIGHCONTRASTW highContrast{};
highContrast.cbSize = sizeof(HIGHCONTRASTW);
if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRASTW), &highContrast, FALSE))
return (highContrast.dwFlags & HCF_HIGHCONTRASTON) == HCF_HIGHCONTRASTON;
return false;
}

void SetTitleBarThemeColor(HWND hWnd, BOOL dark)
{
if (g_buildNumber < 18362)
SetPropW(hWnd, L"UseImmersiveDarkModeColors", reinterpret_cast<HANDLE>(static_cast<intptr_t>(dark)));
else if (_SetWindowCompositionAttribute)
{
WINDOWCOMPOSITIONATTRIBDATA data = { WCA_USEDARKMODECOLORS, &dark, sizeof(dark) };
_SetWindowCompositionAttribute(hWnd, &data);
}
}

void RefreshTitleBarThemeColor(HWND hWnd)
{
BOOL dark = FALSE;
if (_IsDarkModeAllowedForWindow && _ShouldAppsUseDarkMode)
{
if (_IsDarkModeAllowedForWindow(hWnd) && _ShouldAppsUseDarkMode() && !IsHighContrast())
{
dark = TRUE;
}
}

SetTitleBarThemeColor(hWnd, dark);
}

bool IsColorSchemeChangeMessage(LPARAM lParam)
{
bool is = false;
if (lParam && (0 == lstrcmpi(reinterpret_cast<LPCWCH>(lParam), L"ImmersiveColorSet")) && _RefreshImmersiveColorPolicyState)
{
_RefreshImmersiveColorPolicyState();
is = true;
}
if (_GetIsImmersiveColorUsingHighContrast)
_GetIsImmersiveColorUsingHighContrast(IHCM_REFRESH);
return is;
}

bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam)
{
if (message == WM_SETTINGCHANGE)
return IsColorSchemeChangeMessage(lParam);
return false;
}

void AllowDarkModeForApp(bool allow)
{
if (_AllowDarkModeForApp)
_AllowDarkModeForApp(allow);
else if (_SetPreferredAppMode)
_SetPreferredAppMode(allow ? PreferredAppMode::ForceDark : PreferredAppMode::Default);
}

void FlushMenuThemes()
{
if (_FlushMenuThemes)
{
_FlushMenuThemes();
}
}

// limit dark scroll bar to specific windows and their children
std::unordered_set<HWND> g_darkScrollBarWindows;
std::mutex g_darkScrollBarMutex;

void EnableDarkScrollBarForWindowAndChildren(HWND hwnd)
{
std::lock_guard<std::mutex> lock(g_darkScrollBarMutex);
g_darkScrollBarWindows.insert(hwnd);
}

bool IsWindowOrParentUsingDarkScrollBar(HWND hwnd)
{
HWND hwndRoot = GetAncestor(hwnd, GA_ROOT);

std::lock_guard<std::mutex> lock(g_darkScrollBarMutex);
if (g_darkScrollBarWindows.count(hwnd)) {
return true;
}
if (hwnd != hwndRoot && g_darkScrollBarWindows.count(hwndRoot)) {
return true;
}

return false;
}

void FixDarkScrollBar()
{
HMODULE hComctl = LoadLibraryEx(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hComctl)
{
auto addr = FindDelayLoadThunkInModule(hComctl, "uxtheme.dll", 49); // OpenNcThemeData
if (addr)
{
DWORD oldProtect;
if (VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), PAGE_READWRITE, &oldProtect) && _OpenNcThemeData)
{
auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) WINAPI_LAMBDA_RETURN(HTHEME) {
if (wcscmp(classList, L"ScrollBar") == 0)
{
if (IsWindowOrParentUsingDarkScrollBar(hWnd)) {
hWnd = nullptr;
classList = L"Explorer::ScrollBar";
}
}
return _OpenNcThemeData(hWnd, classList);
};

addr->u1.Function = reinterpret_cast<ULONG_PTR>(static_cast<fnOpenNcThemeData>(MyOpenThemeData));
VirtualProtect(addr, sizeof(IMAGE_THUNK_DATA), oldProtect, &oldProtect);
}
}
}
}

constexpr bool CheckBuildNumber(DWORD buildNumber)
{
return (buildNumber == 17763 || // 1809
buildNumber == 18362 || // 1903
buildNumber == 18363 || // 1909
buildNumber == 19041 || // 2004
buildNumber == 19042 || // 20H2
buildNumber == 19043 || // 21H1
buildNumber == 19044 || // 21H2
(buildNumber > 19044 && buildNumber < 22000) || // Windows 10 any version > 21H2
buildNumber >= 22000); // Windows 11 builds
}

bool IsWindows10() // or later OS version
{
return (g_buildNumber >= 17763);
}

bool IsWindows11() // or later OS version
{
return (g_buildNumber >= 22000);
}

DWORD GetWindowsBuildNumber()
{
return g_buildNumber;
}

void InitDarkMode()
{
fnRtlGetNtVersionNumbers RtlGetNtVersionNumbers = nullptr;
HMODULE hNtdllModule = GetModuleHandle(L"ntdll.dll");
if (hNtdllModule)
{
RtlGetNtVersionNumbers = reinterpret_cast<fnRtlGetNtVersionNumbers>(GetProcAddress(hNtdllModule, "RtlGetNtVersionNumbers"));
}

if (RtlGetNtVersionNumbers)
{
DWORD major, minor;
RtlGetNtVersionNumbers(&major, &minor, &g_buildNumber);
g_buildNumber &= ~0xF0000000;
if (major == 10 && minor == 0 && CheckBuildNumber(g_buildNumber))
{
HMODULE hUxtheme = LoadLibraryEx(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (hUxtheme)
{
_OpenNcThemeData = reinterpret_cast<fnOpenNcThemeData>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(49)));
_RefreshImmersiveColorPolicyState = reinterpret_cast<fnRefreshImmersiveColorPolicyState>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104)));
_GetIsImmersiveColorUsingHighContrast = reinterpret_cast<fnGetIsImmersiveColorUsingHighContrast>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(106)));
_ShouldAppsUseDarkMode = reinterpret_cast<fnShouldAppsUseDarkMode>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132)));
_AllowDarkModeForWindow = reinterpret_cast<fnAllowDarkModeForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)));

auto ord135 = GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));
if (g_buildNumber < 18362)
_AllowDarkModeForApp = reinterpret_cast<fnAllowDarkModeForApp>(ord135);
else
_SetPreferredAppMode = reinterpret_cast<fnSetPreferredAppMode>(ord135);

_FlushMenuThemes = reinterpret_cast<fnFlushMenuThemes>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(136)));
_IsDarkModeAllowedForWindow = reinterpret_cast<fnIsDarkModeAllowedForWindow>(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137)));

HMODULE hUser32Module = GetModuleHandleW(L"user32.dll");
if (hUser32Module)
{
_SetWindowCompositionAttribute = reinterpret_cast<fnSetWindowCompositionAttribute>(GetProcAddress(hUser32Module, "SetWindowCompositionAttribute"));
}

if (_OpenNcThemeData &&
_RefreshImmersiveColorPolicyState &&
_ShouldAppsUseDarkMode &&
_AllowDarkModeForWindow &&
(_AllowDarkModeForApp || _SetPreferredAppMode) &&
_FlushMenuThemes &&
_IsDarkModeAllowedForWindow)
{
g_darkModeSupported = true;
}
}
}
}
}

void SetDarkMode(bool useDark, bool fixDarkScrollbar)
{
if (g_darkModeSupported)
{
AllowDarkModeForApp(useDark);
FlushMenuThemes();
if (fixDarkScrollbar)
{
FixDarkScrollBar();
}
g_darkModeEnabled = ShouldAppsUseDarkMode() && !IsHighContrast();
}
}
19 changes: 19 additions & 0 deletions DarkMode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

extern bool g_darkModeSupported;
extern bool g_darkModeEnabled;

bool ShouldAppsUseDarkMode();
bool AllowDarkModeForWindow(HWND hWnd, bool allow);
bool IsHighContrast();
void RefreshTitleBarThemeColor(HWND hWnd);
void SetTitleBarThemeColor(HWND hWnd, BOOL dark);
bool IsColorSchemeChangeMessage(LPARAM lParam);
bool IsColorSchemeChangeMessage(UINT message, LPARAM lParam);
void AllowDarkModeForApp(bool allow);
void EnableDarkScrollBarForWindowAndChildren(HWND hwnd);
void InitDarkMode();
void SetDarkMode(bool useDarkMode, bool fixDarkScrollbar);
bool IsWindows10();
bool IsWindows11();
DWORD GetWindowsBuildNumber();
Loading