Skip to content

Instantly share code, notes, and snippets.

@pa-0
Forked from ericoporto/Dark.md
Created November 15, 2024 11:50
Show Gist options
  • Save pa-0/884ad5916d94e68c04d97dc709a0dda3 to your computer and use it in GitHub Desktop.
Save pa-0/884ad5916d94e68c04d97dc709a0dda3 to your computer and use it in GitHub Desktop.
Win32 Dark Mode

Dark Mode APIs.

API Signatures.

ShouldAppsUseDarkMode()

Checks whether system is using dark mode or not.
Signature:

using fnShouldAppsUseDarkMode = bool (WINAPI*)(); // ordinal 132

AllowDarkModeForWindow(In HWND hWnd, In bool allow)

Switches the control's theme to the dark variant if available.
Signature:

using fnAllowDarkModeForWindow = bool (WINAPI*)(HWND hWnd, bool allow); // ordinal 133

SetPreferredAppMode(In PreferredAppMode appMode)

Dark Context menu for the app. I don't know what else this does.
Signature(1903+):

using fnSetPreferredAppMode = PreferredAppMode(WINAPI*)(PreferredAppMode appMode); // ordinal 135, in 1903

PreferredAppMode enum

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

Actually using them.

First we need to load uxtheme dll as a HMODULE.

HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);

Then we need to get the Process Address of the functions. We already know their ordinal numbers.
First initialize a instance of the signature.

fnSetPreferredAppMode SetPreferredAppMode;

then set the process address by using GetProcAddress

SetPreferredAppMode = (fnSetPreferredAppMode)GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135));

You can do like this for any API. Dont forget to free it

FreeLibrary(hUxtheme);

Usage.

Enables dark context menus which change automatically depending on the theme.

SetPreferredAppMode(PreferredAppMode::AllowDark);

Dark Controls:

First we need to know which all controls support Dark Mode.
If you open aero.msstyles in msstylesEditor, you would be able to see a lot of DarkMode elements.

image
And no you cant just do SetWindowTheme([handle to the control], L"DarkMode_Explorer", NULL);

Button:
SetWindowTheme([handle to the control], L"Explorer", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
Edit:
SetWindowTheme([handle to the control], L"CFD", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
ComboBox:
SetWindowTheme([handle to the control], L"CFD", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
TreeView (maybe):
SetWindowTheme([handle to the control], L"Explorer", NULL);
AllowDarkModeForWindow([handle to the control], true);
SendMessageW([handle to the control], WM_THEMECHANGED, 0, 0);
Dark Scrollbars:

For dark scrollbars you need to iat hook and change the scrollbar theme before its created.
First download this header file and add it to your project. IatHook.h
Then use this function before the creation of HWND.

void FixDarkScrollBar()
{
	HMODULE hComctl = LoadLibraryExW(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))
			{
				auto MyOpenThemeData = [](HWND hWnd, LPCWSTR classList) -> HTHEME {
					if (wcscmp(classList, L"ScrollBar") == 0)
					{
						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);
			}
		}
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment