emqx / MQTTX

A Powerful and All-in-One MQTT 5.0 client toolbox for Desktop, CLI and WebSocket.
https://mqttx.app
Apache License 2.0
3.84k stars 444 forks source link

[Feature] Windows dark mode caption is not changed #1544

Open emako opened 9 months ago

emako commented 9 months ago

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 image

After image

emako commented 9 months ago

Full sample code here:

DarkModeForWindow.zip

You can try it using DarkModeForWindow\bin\Release\net472\DarkModeForWindow.exe.

Launch MQTTX and then launch DarkModeForWindow.exe.

ysfscream commented 9 months ago

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!

emako commented 9 months ago

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 "electron-builder@23.0.2" is incompatible with requested version "electron-builder@^22.2.0"
[3/6] Fetching packages...
[4/6] Linking dependencies...
warning "@vue/cli-plugin-unit-mocha > mocha-webpack@2.0.0-beta.0" has unmet peer dependency "webpack@^4.0.0".
warning " > monaco-editor-webpack-plugin@4.0.0" has unmet peer dependency "webpack@^4.5.0 || 5.x".
warning " > sass-loader@8.0.2" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".
warning "typeorm-uml > @oclif/command@1.8.0" 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)
ysfscream commented 9 months ago

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.

emako commented 9 months ago
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.

ysfscream commented 9 months ago

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.

emako commented 9 months ago
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 ...

emako commented 9 months ago

https://github.com/electron/electron/issues/26910

emako commented 9 months ago
  1. set ELECTRON_MIRROR=http://npm.taobao.org/mirrors/electron/ in environment variables
  2. execute: yarn install
  3. npm install sqlite3 --registry=https://registry.npm.taobao.org/ --node_sqlite3_binary_host_mirror=http://npm.taobao.org/mirrors
emako commented 9 months ago

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);
  }
}

image

emako commented 4 months ago

This software support dark mode title bar https://github.com/revezone/revezone

emako commented 4 months ago

image

emako commented 4 months ago

https://www.electronjs.org/docs/latest/tutorial/dark-mode

emako commented 4 months ago

No one wants to fix it.

ysfscream commented 4 months ago

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.

SaifAqqad commented 2 months ago

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 😅

image

emako commented 1 month ago

Here a temp method for using dark mode titlebar https://github.com/lemutec/MakeDark