[Feature] Windows dark mode caption is not changed
Motivation
Fix the dark mode in windows OS.
Detailed design
Use Win32API to change the caption background color.
Here are some C# sample code:
// NativeMethods
using System.Runtime.InteropServices;
namespace DarkModeForWindow;
internal static class NativeMethods
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindow(IntPtr hWnd);
[DllImport("dwmapi.dll", PreserveSig = true)]
public static extern int DwmSetWindowAttribute(nint hwnd, DwmWindowAttribute attr, ref int attrValue, int attrSize);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation);
public static bool IsWindows10Version1809OrAbove()
{
RTL_OSVERSIONINFOEX versionInfo = new()
{
dwOSVersionInfoSize = (uint)Marshal.SizeOf(typeof(RTL_OSVERSIONINFOEX)),
};
if (RtlGetVersion(ref versionInfo) == 0)
{
// Windows 10 1809
return versionInfo.dwMajorVersion >= 10 && versionInfo.dwBuildNumber >= 17763;
}
return false;
}
public static bool EnableDarkModeForWindow(nint hWnd, bool enable)
{
if (IsWindows10Version1809OrAbove())
{
int darkMode = enable ? 1 : 0;
int hr = DwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, ref darkMode, sizeof(int));
return hr >= 0;
}
return true;
}
public enum DwmWindowAttribute : uint
{
NCRenderingEnabled = 1,
NCRenderingPolicy,
TransitionsForceDisabled,
AllowNCPaint,
CaptionButtonBounds,
NonClientRtlLayout,
ForceIconicRepresentation,
Flip3DPolicy,
ExtendedFrameBounds,
HasIconicBitmap,
DisallowPeek,
ExcludedFromPeek,
Cloak,
Cloaked,
FreezeRepresentation,
PassiveUpdateMode,
UseHostBackdropBrush,
UseImmersiveDarkMode = 20,
WindowCornerPreference = 33,
BorderColor,
CaptionColor,
TextColor,
VisibleFrameBorderThickness,
SystemBackdropType,
Last,
}
[StructLayout(LayoutKind.Sequential)]
public struct RTL_OSVERSIONINFOEX
{
public uint dwOSVersionInfoSize;
public uint dwMajorVersion;
public uint dwMinorVersion;
public uint dwBuildNumber;
public uint dwPlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
}
}
// How to use win32api
nint hWnd = NativeMethods.FindWindow(null!, "MQTTX"); // May you can get the hWnd by Electron API
if (NativeMethods.IsWindow(hWnd))
{
NativeMethods.EnableDarkModeForWindow(hWnd, true);
}
Alternatives
empty
More detail (optional)
The result of the c# sample code:
Before
After
Full sample code here:
You can try it using DarkModeForWindow\bin\Release\net472\DarkModeForWindow.exe.
Launch MQTTX and then launch DarkModeForWindow.exe.
Thank you very much for bringing up this issue and for the solution you've provided. @emako
I want to point out that MQTTX is primarily developed using JavaScript within the Electron framework. As such, I need to become more familiar with C#. In light of this, is there another possible approach to change the dark mode caption color, especially one that could be implemented within the JavaScript or Electron framework?
Also, your participation would be most welcome if you're interested in contributing to this feature enhancement. Your expertise and experience would be a valuable asset, and we look forward to your further involvement.
Thanks again for your contribution and support!
This requires me to take some time to try electron.
Meet something error on yarn install:
D:\Github\MQTTX>yarn install
yarn install v1.22.19
[1/6] Validating package.json...
[2/6] Resolving packages...
warning Resolution field "[email protected]" is incompatible with requested version "electron-builder@^22.2.0"
[3/6] Fetching packages...
[4/6] Linking dependencies...
warning "@vue/cli-plugin-unit-mocha > [email protected]" has unmet peer dependency "webpack@^4.0.0".
warning " > [email protected]" has unmet peer dependency "webpack@^4.5.0 || 5.x".
warning " > [email protected]" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".
warning "typeorm-uml > @oclif/[email protected]" has unmet peer dependency "@oclif/config@^1".
[5/6] Building fresh packages...
[10/11] ⠂ husky
[-/11] ⠂ waiting...
[3/11] ⠂ sqlite3
[11/11] ⠂ electron
error D:\Github\MQTTX\node_modules\electron: Command failed.
Exit code: 1
Command: node install.js
Arguments:
Directory: D:\Github\MQTTX\node_modules\electron
Output:
RequestError: unable to verify the first certificate
at ClientRequest.<anonymous> (D:\Github\MQTTX\node_modules\got\source\request-as-event-emitter.js:178:14)
at Object.onceWrapper (node:events:629:26)
at ClientRequest.emit (node:events:526:35)
at origin.emit (D:\Github\MQTTX\node_modules\@szmarczak\http-timer\source\index.js:37:11)
at TLSSocket.socketErrorListener (node:_http_client:495:9)
at TLSSocket.emit (node:events:514:28)
This issue may be due to network problems. As suggested, you can try manually installing it if you like. Go to your project's node_modules\electron directory and then run node install.js. This should manually initiate the installation process for Electron.
D:\Github\MQTTX>cd node_modules\electron
D:\Github\MQTTX\node_modules\electron>node install.js
RequestError: unable to verify the first certificate
at ClientRequest.<anonymous> (D:\Github\MQTTX\node_modules\got\source\request-as-event-emitter.js:178:14)
at Object.onceWrapper (node:events:629:26)
at ClientRequest.emit (node:events:526:35)
at origin.emit (D:\Github\MQTTX\node_modules\@szmarczak\http-timer\source\index.js:37:11)
at TLSSocket.socketErrorListener (node:_http_client:495:9)
at TLSSocket.emit (node:events:514:28)
at emitErrorNT (node:internal/streams/destroy:151:8)
at emitErrorCloseNT (node:internal/streams/destroy:116:3)
at process.processTicksAndRejections (node:internal/process/task_queues:82:21)
I have no idea above it.
Network issues likely cause this error. To resolve this, please make sure you are using Node.js v16. Sometimes, specific versions of Node.js handle SSL/TLS certificate verification better.
If the problem persists after updating to Node.js v16, you could modify npm's configuration to ignore SSL certificate validation. This can be done with the following command:
npm config set strict-ssl false
Just to let you know, while this method might solve your immediate issue, it will cause your npm client to trust all SSL certificates, which could pose security risks. Therefore, it should be considered a temporary solution rather than a long-term practice. Once the issue is resolved, it is advisable to revert this setting to its default state:
npm config set strict-ssl true
Hope this helps you resolve the issue.
D:\Github\MQTTX>node -v
v20.10.0
npm config set strict-ssl false not work for node v20.10.0.
Have a try with v16 ...
https://github.com/electron/electron/issues/26910
- set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ in environment variables
- execute: yarn install
- npm install sqlite3 --registry=https://registry.npm.taobao.org/ --node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors
I'm not familiar with how to call the Win32 API in an electron environment.
Some demo code record following...
in package.json
"ffi-napi": "4.0.3",
"ref-napi": "3.0.3"
in background.ts
// ...
win = new BrowserWindow
// ...
console.warn(process.platform)
console.warn(os.release())
if (process.platform === 'win32' ) {
win?.on('ready-to-show', () => {
const hWnd = win?.getNativeWindowHandle()
console.log('Window handle:', hWnd)
WindowsDarkCaption.makeUp(hWnd);
})
}
class WindowsDarkCaption {
public static makeUp(hWnd : Buffer | undefined | number) : void {
if (!hWnd) {
return
}
const ffi = require('ffi-napi')
const ref = require('ref-napi')
if (typeof hWnd === 'object') {
hWnd = ref.address(hWnd);
}
const DwmWindowAttribute = {
UseImmersiveDarkMode: 20,
};
const dwmapi = new ffi.Library('dwmapi', {
'DwmSetWindowAttribute': ['int', ['pointer', 'uint', 'pointer', 'int']],
});
function dwmSetWindowAttribute(hWnd: number, attr: number, attrValue: number, attrSize: number) {
return dwmapi.DwmSetWindowAttribute(hWnd, attr, attrValue, attrSize);
}
function enableDarkModeForWindow(hWnd : number, enable : boolean) {
const darkMode = enable ? 1 : 0;
return dwmSetWindowAttribute(hWnd, DwmWindowAttribute.UseImmersiveDarkMode, darkMode, 4) >= 0;
}
enableDarkModeForWindow(<number>hWnd, true);
}
}
This software support dark mode title bar https://github.com/revezone/revezone
https://www.electronjs.org/docs/latest/tutorial/dark-mode
No one wants to fix it.
Sorry for the late reply. I have scheduled it for a future version, but currently, due to other high-priority tasks, we do not have the time to perfect it. Please be patient, and thank you for your suggestion.
Upgrading the electron version should fix this, Although the whole app will stop working as there are breaking changes in the code when upgrading from v13 to v31 😅
Here a temp method for using dark mode titlebar https://github.com/lemutec/MakeDark