From c5ad7de9cf9d68bb7a0ca247956ad97cd9be7c60 Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sat, 8 Oct 2022 10:10:39 +1100 Subject: [PATCH] Menu to follow theme --- v2/internal/platform/systray/windows.go | 4 + v2/internal/platform/win32/consts.go | 78 +++++++++++++++++ v2/internal/platform/win32/theme.go | 110 ++++++++++++++++++++++++ 3 files changed, 192 insertions(+) diff --git a/v2/internal/platform/systray/windows.go b/v2/internal/platform/systray/windows.go index 0273a1097..3fec9a59f 100644 --- a/v2/internal/platform/systray/windows.go +++ b/v2/internal/platform/systray/windows.go @@ -336,6 +336,10 @@ func (p *Systray) updateIcon() error { return p.setIcon(newIcon) } +func (p *Systray) updateTheme() { + //win32.SetTheme(p.hwnd, win32.IsCurrentlyDarkMode()) +} + func RegisterWindow(name string, proc win32.WindowProc) (win32.HINSTANCE, error) { instance, exists := windowClasses[name] if exists { diff --git a/v2/internal/platform/win32/consts.go b/v2/internal/platform/win32/consts.go index 2617405fa..d9055e240 100644 --- a/v2/internal/platform/win32/consts.go +++ b/v2/internal/platform/win32/consts.go @@ -1,6 +1,8 @@ package win32 import ( + "github.com/wailsapp/wails/v2/internal/system/operatingsystem" + "golang.org/x/sys/windows" "syscall" "unsafe" ) @@ -28,11 +30,79 @@ var ( procTranslateMessage = moduser32.NewProc("TranslateMessage") procDispatchMessage = moduser32.NewProc("DispatchMessageW") procPostQuitMessage = moduser32.NewProc("PostQuitMessage") + procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW") modshell32 = syscall.NewLazyDLL("shell32.dll") procShellNotifyIcon = modshell32.NewProc("Shell_NotifyIconW") + + moddwmapi = syscall.NewLazyDLL("dwmapi.dll") + procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") + + moduxtheme = syscall.NewLazyDLL("uxtheme.dll") + procSetWindowTheme = moduxtheme.NewProc("SetWindowTheme") + + AllowDarkModeForWindow func(HWND, bool) uintptr + SetPreferredAppMode func(int32) uintptr ) +type PreferredAppMode = int32 + +const ( + PreferredAppModeDefault PreferredAppMode = iota + PreferredAppModeAllowDark + PreferredAppModeForceDark + PreferredAppModeForceLight + PreferredAppModeMax +) + +/* +RtlGetNtVersionNumbers = void (LPDWORD major, LPDWORD minor, LPDWORD build) // 1809 17763 +ShouldAppsUseDarkMode = bool () // ordinal 132 +AllowDarkModeForWindow = bool (HWND hWnd, bool allow) // ordinal 133 +AllowDarkModeForApp = bool (bool allow) // ordinal 135, removed since 18334 +FlushMenuThemes = void () // ordinal 136 +RefreshImmersiveColorPolicyState = void () // ordinal 104 +IsDarkModeAllowedForWindow = bool (HWND hWnd) // ordinal 137 +GetIsImmersiveColorUsingHighContrast = bool (IMMERSIVE_HC_CACHE_MODE mode) // ordinal 106 +OpenNcThemeData = HTHEME (HWND hWnd, LPCWSTR pszClassList) // ordinal 49 +// Insider 18290 +ShouldSystemUseDarkMode = bool () // ordinal 138 +// Insider 18334 +SetPreferredAppMode = PreferredAppMode (PreferredAppMode appMode) // ordinal 135, since 18334 +IsDarkModeAllowedForApp = bool () // ordinal 139 +*/ +func init() { + if IsWindowsVersionAtLeast(10, 0, 18334) { + + // AllowDarkModeForWindow is only available on Windows 10+ + uxtheme, err := windows.LoadLibrary("uxtheme.dll") + if err == nil { + procAllowDarkModeForWindow, err := windows.GetProcAddressByOrdinal(uxtheme, uintptr(133)) + if err == nil { + AllowDarkModeForWindow = func(hwnd HWND, allow bool) uintptr { + var allowInt int32 + if allow { + allowInt = 1 + } + ret, _, _ := syscall.SyscallN(procAllowDarkModeForWindow, uintptr(hwnd), uintptr(allowInt)) + return ret + } + } + } + + // SetPreferredAppMode is only available on Windows 10+ + procSetPreferredAppMode, err := windows.GetProcAddressByOrdinal(uxtheme, uintptr(135)) + if err == nil { + SetPreferredAppMode = func(mode int32) uintptr { + ret, _, _ := syscall.SyscallN(procSetPreferredAppMode, uintptr(mode)) + return ret + } + SetPreferredAppMode(PreferredAppModeAllowDark) + } + } + +} + type HANDLE uintptr type HINSTANCE HANDLE type HICON HANDLE @@ -117,6 +187,14 @@ const ( CS_HREDRAW = 0x0002 ) +var windowsVersion, _ = operatingsystem.GetWindowsVersionInfo() + +func IsWindowsVersionAtLeast(major, minor, buildNumber int) bool { + return windowsVersion.Major >= major && + windowsVersion.Minor >= minor && + windowsVersion.Build >= buildNumber +} + type WindowProc func(hwnd HWND, msg uint32, wparam, lparam uintptr) uintptr func GetModuleHandle(value uintptr) uintptr { diff --git a/v2/internal/platform/win32/theme.go b/v2/internal/platform/win32/theme.go index d5a23eca4..c15d73a3a 100644 --- a/v2/internal/platform/win32/theme.go +++ b/v2/internal/platform/win32/theme.go @@ -2,8 +2,100 @@ package win32 import ( "golang.org/x/sys/windows/registry" + "unsafe" ) +type DWMWINDOWATTRIBUTE int32 + +const DwmwaUseImmersiveDarkModeBefore20h1 DWMWINDOWATTRIBUTE = 19 +const DwmwaUseImmersiveDarkMode DWMWINDOWATTRIBUTE = 20 +const DwmwaBorderColor DWMWINDOWATTRIBUTE = 34 +const DwmwaCaptionColor DWMWINDOWATTRIBUTE = 35 +const DwmwaTextColor DWMWINDOWATTRIBUTE = 36 +const DwmwaSystemBackdropType DWMWINDOWATTRIBUTE = 38 + +const SPI_GETHIGHCONTRAST = 0x0042 +const HCF_HIGHCONTRASTON = 0x00000001 + +// BackdropType defines the type of translucency we wish to use +type BackdropType int32 + +func dwmSetWindowAttribute(hwnd HWND, dwAttribute DWMWINDOWATTRIBUTE, pvAttribute unsafe.Pointer, cbAttribute uintptr) { + ret, _, err := procDwmSetWindowAttribute.Call( + uintptr(hwnd), + uintptr(dwAttribute), + uintptr(pvAttribute), + cbAttribute) + if ret != 0 { + _ = err + // println(err.Error()) + } +} + +func SupportsThemes() bool { + // We can't support Windows versions before 17763 + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsCustomThemes() bool { + return IsWindowsVersionAtLeast(10, 0, 17763) +} + +func SupportsBackdropTypes() bool { + return IsWindowsVersionAtLeast(10, 0, 22621) +} + +func SupportsImmersiveDarkMode() bool { + return IsWindowsVersionAtLeast(10, 0, 18985) +} + +func SetTheme(hwnd HWND, useDarkMode bool) { + if SupportsThemes() { + attr := DwmwaUseImmersiveDarkModeBefore20h1 + if SupportsImmersiveDarkMode() { + attr = DwmwaUseImmersiveDarkMode + } + var winDark int32 + if useDarkMode { + winDark = 1 + } + dwmSetWindowAttribute(hwnd, attr, unsafe.Pointer(&winDark), unsafe.Sizeof(winDark)) + } +} + +func EnableTranslucency(hwnd HWND, backdrop BackdropType) { + if SupportsBackdropTypes() { + dwmSetWindowAttribute(hwnd, DwmwaSystemBackdropType, unsafe.Pointer(&backdrop), unsafe.Sizeof(backdrop)) + } else { + println("Warning: Translucency type unavailable on Windows < 22621") + } +} + +func SetTitleBarColour(hwnd HWND, titleBarColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaCaptionColor, unsafe.Pointer(&titleBarColour), unsafe.Sizeof(titleBarColour)) +} + +func SetTitleTextColour(hwnd HWND, titleTextColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaTextColor, unsafe.Pointer(&titleTextColour), unsafe.Sizeof(titleTextColour)) +} + +func SetBorderColour(hwnd HWND, titleBorderColour int32) { + dwmSetWindowAttribute(hwnd, DwmwaBorderColor, unsafe.Pointer(&titleBorderColour), unsafe.Sizeof(titleBorderColour)) +} + +func SetWindowTheme(hwnd HWND, appName string, subIdList string) uintptr { + var subID uintptr + if subIdList != "" { + subID = MustStringToUTF16uintptr(subIdList) + } + ret, _, _ := procSetWindowTheme.Call( + uintptr(hwnd), + MustStringToUTF16uintptr(appName), + subID, + ) + + return ret +} func IsCurrentlyDarkMode() bool { key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE) if err != nil { @@ -17,3 +109,21 @@ func IsCurrentlyDarkMode() bool { } return AppsUseLightTheme == 0 } + +type highContrast struct { + CbSize uint32 + DwFlags uint32 + LpszDefaultScheme *int16 +} + +func IsCurrentlyHighContrastMode() bool { + var result highContrast + result.CbSize = uint32(unsafe.Sizeof(result)) + res, _, err := procSystemParametersInfo.Call(SPI_GETHIGHCONTRAST, uintptr(result.CbSize), uintptr(unsafe.Pointer(&result)), 0) + if res == 0 { + _ = err + return false + } + r := result.DwFlags&HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON + return r +}