|
| 1 | +{ |
| 2 | + Double Commander |
| 3 | + ------------------------------------------------------------------------- |
| 4 | + Dark mode support unit (Windows 10 + Qt5). |
| 5 | +
|
| 6 | + Copyright (C) 2019-2021 Richard Yu |
| 7 | + Copyright (C) 2019-2022 Alexander Koblov (alexx2000@mail.ru) |
| 8 | +
|
| 9 | + Permission is hereby granted, free of charge, to any person obtaining a copy |
| 10 | + of this software and associated documentation files (the "Software"), to deal |
| 11 | + in the Software without restriction, including without limitation the rights |
| 12 | + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 13 | + copies of the Software, and to permit persons to whom the Software is |
| 14 | + furnished to do so, subject to the following conditions: |
| 15 | +
|
| 16 | + The above copyright notice and this permission notice shall be included in all |
| 17 | + copies or substantial portions of the Software. |
| 18 | +
|
| 19 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 20 | + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 21 | + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 22 | + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 23 | + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 24 | + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 25 | + SOFTWARE. |
| 26 | +} |
| 27 | + |
| 28 | +unit uDarkStyle; |
| 29 | + |
| 30 | +{$mode delphi} |
| 31 | + |
| 32 | +interface |
| 33 | + |
| 34 | +uses |
| 35 | + Classes, SysUtils, Windows; |
| 36 | + |
| 37 | +var |
| 38 | + g_buildNumber: DWORD = 0; |
| 39 | + //g_darkModeEnabled: bool = false; |
| 40 | + g_darkModeSupported: bool = false; |
| 41 | + //gAppMode: integer = 1; |
| 42 | + |
| 43 | +{$IF DEFINED(LCLQT5) OR DEFINED(LCLQT6)} |
| 44 | +procedure ApplyDarkStyle; |
| 45 | +{$ENDIF} |
| 46 | + |
| 47 | +procedure RefreshTitleBarThemeColor(hWnd: HWND); |
| 48 | +function AllowDarkModeForWindow(hWnd: HWND; allow: bool): bool; |
| 49 | +procedure InitDarkMode; |
| 50 | + |
| 51 | +implementation |
| 52 | + |
| 53 | +uses |
| 54 | + UxTheme, JwaWinUser, FileInfo, uDarkStyleParams |
| 55 | + {$IF DEFINED(LCLQT5)} |
| 56 | + ,Qt5 |
| 57 | + {$ENDIF} |
| 58 | + {$IF DEFINED(LCLQT6)} |
| 59 | + ,Qt6 |
| 60 | + {$ENDIF} |
| 61 | + ; |
| 62 | + |
| 63 | +var |
| 64 | + AppMode: TPreferredAppMode; |
| 65 | + |
| 66 | +var |
| 67 | + RtlGetNtVersionNumbers: procedure(major, minor, build: LPDWORD); stdcall; |
| 68 | + DwmSetWindowAttribute: function(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HRESULT; stdcall; |
| 69 | + // 1809 17763 |
| 70 | + _ShouldAppsUseDarkMode: function(): bool; stdcall; // ordinal 132 |
| 71 | + _AllowDarkModeForWindow: function(hWnd: HWND; allow: bool): bool; stdcall; // ordinal 133 |
| 72 | + _AllowDarkModeForApp: function(allow: bool): bool; stdcall; // ordinal 135, removed since 18334 |
| 73 | + _RefreshImmersiveColorPolicyState: procedure(); stdcall; // ordinal 104 |
| 74 | + _IsDarkModeAllowedForWindow: function(hWnd: HWND): bool; stdcall; // ordinal 137 |
| 75 | + // Insider 18334 |
| 76 | + _SetPreferredAppMode: function(appMode: TPreferredAppMode): TPreferredAppMode; stdcall; // ordinal 135, since 18334 |
| 77 | + |
| 78 | +function AllowDarkModeForWindow(hWnd: HWND; allow: bool): bool; |
| 79 | +begin |
| 80 | + if (g_darkModeSupported) then |
| 81 | + Result:= _AllowDarkModeForWindow(hWnd, allow) |
| 82 | + else |
| 83 | + Result:= false; |
| 84 | +end; |
| 85 | + |
| 86 | +function IsHighContrast(): bool; |
| 87 | +var |
| 88 | + highContrast: HIGHCONTRASTW; |
| 89 | +begin |
| 90 | + highContrast.cbSize:= SizeOf(HIGHCONTRASTW); |
| 91 | + if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, SizeOf(highContrast), @highContrast, 0)) then |
| 92 | + Result:= (highContrast.dwFlags and HCF_HIGHCONTRASTON <> 0) |
| 93 | + else |
| 94 | + Result:= false; |
| 95 | +end; |
| 96 | + |
| 97 | +function ShouldAppsUseDarkMode: Boolean; |
| 98 | +var |
| 99 | + bb:bool; |
| 100 | +begin |
| 101 | + bb:=_ShouldAppsUseDarkMode(); |
| 102 | + Result:= (_ShouldAppsUseDarkMode() or (AppMode = pamForceDark)) and not IsHighContrast(); |
| 103 | +end; |
| 104 | + |
| 105 | +procedure RefreshTitleBarThemeColor(hWnd: HWND); |
| 106 | +const |
| 107 | + DWMWA_USE_IMMERSIVE_DARK_MODE_OLD = 19; |
| 108 | + DWMWA_USE_IMMERSIVE_DARK_MODE_NEW = 20; |
| 109 | +var |
| 110 | + dark: BOOL; |
| 111 | + dwAttribute: DWORD; |
| 112 | +begin |
| 113 | + dark:= (_IsDarkModeAllowedForWindow(hWnd) and ShouldAppsUseDarkMode); |
| 114 | + |
| 115 | + if (Win32BuildNumber < 19041) then |
| 116 | + dwAttribute:= DWMWA_USE_IMMERSIVE_DARK_MODE_OLD |
| 117 | + else begin |
| 118 | + dwAttribute:= DWMWA_USE_IMMERSIVE_DARK_MODE_NEW; |
| 119 | + end; |
| 120 | + |
| 121 | + DwmSetWindowAttribute(hwnd, dwAttribute, @dark, SizeOf(dark)); |
| 122 | +end; |
| 123 | + |
| 124 | +procedure AllowDarkModeForApp(allow: bool); |
| 125 | +begin |
| 126 | + if Assigned(_AllowDarkModeForApp) then |
| 127 | + _AllowDarkModeForApp(allow) |
| 128 | + else if Assigned(_SetPreferredAppMode) then |
| 129 | + begin |
| 130 | + if (allow) then |
| 131 | + _SetPreferredAppMode(AppMode) |
| 132 | + else |
| 133 | + _SetPreferredAppMode(pamDefault); |
| 134 | + end; |
| 135 | +end; |
| 136 | + |
| 137 | +{$IF DEFINED(LCLQT5) OR DEFINED(LCLQT6)} |
| 138 | +procedure ApplyDarkStyle; |
| 139 | +const |
| 140 | + StyleName: WideString = 'Fusion'; |
| 141 | +var |
| 142 | + AColor: TQColor; |
| 143 | + APalette: QPaletteH; |
| 144 | + |
| 145 | + function QColor(R: Integer; G: Integer; B: Integer; A: Integer = 255): PQColor; |
| 146 | + begin |
| 147 | + Result:= @AColor; |
| 148 | + QColor_fromRgb(Result, R, G, B, A); |
| 149 | + end; |
| 150 | + |
| 151 | +begin |
| 152 | + //g_darkModeEnabled:= True; |
| 153 | + |
| 154 | + QApplication_setStyle(QStyleFactory_create(@StyleName)); |
| 155 | + |
| 156 | + APalette:= QPalette_Create(); |
| 157 | + |
| 158 | + // Modify palette to dark |
| 159 | + if (AppMode = pamForceDark) then |
| 160 | + begin |
| 161 | + // DarkMode Pallete |
| 162 | + QPalette_setColor(APalette, QPaletteWindow, QColor(53, 53, 53)); |
| 163 | + QPalette_setColor(APalette, QPaletteWindowText, QColor(255, 255, 255)); |
| 164 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteWindowText, QColor(127, 127, 127)); |
| 165 | + QPalette_setColor(APalette, QPaletteBase, QColor(42, 42, 42)); |
| 166 | + QPalette_setColor(APalette, QPaletteAlternateBase, QColor(66, 66, 66)); |
| 167 | + QPalette_setColor(APalette, QPaletteToolTipBase, QColor(255, 255, 255)); |
| 168 | + QPalette_setColor(APalette, QPaletteToolTipText, QColor(53, 53, 53)); |
| 169 | + QPalette_setColor(APalette, QPaletteText, QColor(255, 255, 255)); |
| 170 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteText, QColor(127, 127, 127)); |
| 171 | + QPalette_setColor(APalette, QPaletteDark, QColor(35, 35, 35)); |
| 172 | + QPalette_setColor(APalette, QPaletteLight, QColor(66, 66, 66)); |
| 173 | + QPalette_setColor(APalette, QPaletteShadow, QColor(20, 20, 20)); |
| 174 | + QPalette_setColor(APalette, QPaletteButton, QColor(53, 53, 53)); |
| 175 | + QPalette_setColor(APalette, QPaletteButtonText, QColor(255, 255, 255)); |
| 176 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteButtonText, QColor(127, 127, 127)); |
| 177 | + QPalette_setColor(APalette, QPaletteBrightText, QColor(255, 0, 0)); |
| 178 | + QPalette_setColor(APalette, QPaletteLink, QColor(42, 130, 218)); |
| 179 | + QPalette_setColor(APalette, QPaletteHighlight, QColor(42, 130, 218)); |
| 180 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteHighlight, QColor(80, 80, 80)); |
| 181 | + QPalette_setColor(APalette, QPaletteHighlightedText, QColor(255, 255, 255)); |
| 182 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteHighlightedText, QColor(127, 127, 127)); |
| 183 | + end |
| 184 | + else |
| 185 | + begin |
| 186 | + // LightMode Pallete |
| 187 | + QPalette_setColor(APalette, QPaletteWindow, QColor(240, 240, 240)); |
| 188 | + QPalette_setColor(APalette, QPaletteWindowText, QColor(0, 0, 0)); |
| 189 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteWindowText, QColor(127, 127, 127)); |
| 190 | + QPalette_setColor(APalette, QPaletteBase, QColor(225, 225, 225)); |
| 191 | + QPalette_setColor(APalette, QPaletteAlternateBase, QColor(255, 255, 255)); |
| 192 | + QPalette_setColor(APalette, QPaletteToolTipBase, QColor(255, 255, 255)); |
| 193 | + QPalette_setColor(APalette, QPaletteToolTipText, QColor(0, 0, 0)); |
| 194 | + QPalette_setColor(APalette, QPaletteText, QColor(0, 0, 0)); |
| 195 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteText, QColor(127, 127, 127)); |
| 196 | + QPalette_setColor(APalette, QPaletteDark, QColor(200, 200, 200)); |
| 197 | + QPalette_setColor(APalette, QPaletteLight, QColor(255, 255, 255)); |
| 198 | + QPalette_setColor(APalette, QPaletteShadow, QColor(220, 220, 220)); |
| 199 | + QPalette_setColor(APalette, QPaletteButton, QColor(240, 240, 240)); |
| 200 | + QPalette_setColor(APalette, QPaletteButtonText, QColor(0, 0, 0)); |
| 201 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteButtonText, QColor(127, 127, 127)); |
| 202 | + QPalette_setColor(APalette, QPaletteBrightText, QColor(255, 0, 0)); |
| 203 | + QPalette_setColor(APalette, QPaletteLink, QColor(42, 130, 218)); |
| 204 | + QPalette_setColor(APalette, QPaletteHighlight, QColor(42, 130, 218)); |
| 205 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteHighlight, QColor(200, 200, 200)); |
| 206 | + QPalette_setColor(APalette, QPaletteHighlightedText, QColor(255, 255, 255)); |
| 207 | + QPalette_setColor(APalette, QPaletteDisabled, QPaletteHighlightedText, QColor(127, 127, 127)); |
| 208 | + end; |
| 209 | + |
| 210 | + QApplication_setPalette(APalette); |
| 211 | +end; |
| 212 | +{$ENDIF} |
| 213 | + |
| 214 | +const |
| 215 | + LOAD_LIBRARY_SEARCH_SYSTEM32 = $800; |
| 216 | + |
| 217 | +function CheckBuildNumber(buildNumber: DWORD): Boolean; inline; |
| 218 | +begin |
| 219 | + Result := (buildNumber = 17763) or // Win 10: 1809 |
| 220 | + (buildNumber = 18362) or // Win 10: 1903 & 1909 |
| 221 | + (buildNumber = 19041) or // Win 10: 2004 & 20H2 & 21H1 & 21H2 |
| 222 | + (buildNumber = 22000) or // Win 11: 21H2 |
| 223 | + (buildNumber > 22000); // Win 11: Insider Preview |
| 224 | +end; |
| 225 | + |
| 226 | +function GetBuildNumber(Instance: THandle): DWORD; |
| 227 | +begin |
| 228 | + try |
| 229 | + with TVersionInfo.Create do |
| 230 | + try |
| 231 | + Load(Instance); |
| 232 | + Result:= FixedInfo.FileVersion[2]; |
| 233 | + finally |
| 234 | + Free; |
| 235 | + end; |
| 236 | + except |
| 237 | + Exit(0); |
| 238 | + end; |
| 239 | +end; |
| 240 | + |
| 241 | +procedure InitDarkMode(); |
| 242 | +var |
| 243 | + hUxtheme: HMODULE; |
| 244 | + major, minor, build: DWORD; |
| 245 | +begin |
| 246 | + @RtlGetNtVersionNumbers := GetProcAddress(GetModuleHandleW('ntdll.dll'), 'RtlGetNtVersionNumbers'); |
| 247 | + if Assigned(RtlGetNtVersionNumbers) then |
| 248 | + begin |
| 249 | + RtlGetNtVersionNumbers(@major, @minor, @build); |
| 250 | + |
| 251 | + if (major = 10) and (minor = 0) then |
| 252 | + begin |
| 253 | + hUxtheme := LoadLibraryExW('uxtheme.dll', 0, LOAD_LIBRARY_SEARCH_SYSTEM32); |
| 254 | + if (hUxtheme <> 0) then |
| 255 | + begin |
| 256 | + g_buildNumber:= GetBuildNumber(hUxtheme); |
| 257 | + |
| 258 | + if CheckBuildNumber(g_buildNumber) then |
| 259 | + begin |
| 260 | + @_RefreshImmersiveColorPolicyState := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(104)); |
| 261 | + @_ShouldAppsUseDarkMode := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(132)); |
| 262 | + @_AllowDarkModeForWindow := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133)); |
| 263 | + |
| 264 | + if (g_buildNumber < 18362) then |
| 265 | + @_AllowDarkModeForApp := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)) |
| 266 | + else |
| 267 | + @_SetPreferredAppMode := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135)); |
| 268 | + |
| 269 | + @_IsDarkModeAllowedForWindow := GetProcAddress(hUxtheme, MAKEINTRESOURCEA(137)); |
| 270 | + |
| 271 | + @DwmSetWindowAttribute := GetProcAddress(LoadLibrary('dwmapi.dll'), 'DwmSetWindowAttribute'); |
| 272 | + |
| 273 | + if Assigned(_RefreshImmersiveColorPolicyState) and |
| 274 | + Assigned(_ShouldAppsUseDarkMode) and |
| 275 | + Assigned(_AllowDarkModeForWindow) and |
| 276 | + (Assigned(_AllowDarkModeForApp) or Assigned(_SetPreferredAppMode)) and |
| 277 | + Assigned(_IsDarkModeAllowedForWindow) then |
| 278 | + begin |
| 279 | + g_darkModeSupported := true; |
| 280 | + AppMode := PreferredAppMode; |
| 281 | + if AppMode <> pamForceLight then |
| 282 | + begin |
| 283 | + AllowDarkModeForApp(true); |
| 284 | + _RefreshImmersiveColorPolicyState(); |
| 285 | + IsDarkModeEnabled := ShouldAppsUseDarkMode; |
| 286 | + if IsDarkModeEnabled then AppMode := pamForceDark; |
| 287 | + end; |
| 288 | + end; |
| 289 | + end; |
| 290 | + end; |
| 291 | + end; |
| 292 | + end; |
| 293 | +end; |
| 294 | + |
| 295 | +initialization |
| 296 | +end. |
| 297 | + |
0 commit comments