Sign in Join
Wiki copytemporary

Dark theme: what changed

Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
1-How to detect whether the operating system is using a dark theme?
1+# Detect Dark Theme
22
3+How to detect whether the operating system is using a dark theme?
4+
35## Universal method
46
5-The following method is based on the GUI colors. It works on all platforms but those colors are not updated when the theme is changed while the application is running. Thus it cannot allow to detect if the dark mode is toggled.
7+The following method is based on the GUI colors. It works on all platforms but those colors are not updated when the theme is changed while the application is running. Thus it cannot allow to detect if the dark mode is toggled.
68
79### Example 1
8-
9-``` pascal
10+```
1011// by "Alextp" from Lazarus forum
11-function IsDarkTheme: boolean;
12-const
13- cMax = $A0;
14-var
15- N: TColor;
16-begin
17- N:= ColorToRGB(clWindow);
18- Result:= (Red(N)<cMax) and (Green(N)<cMax) and (Blue(N)<cMax);
19-end;
12+ function IsDarkTheme: boolean;
13+ const
14+ cMax = $A0;
15+ var
16+ N: TColor;
17+ begin
18+ N:= ColorToRGB(clWindow);
19+ Result:= (Red(N)<cMax) and (Green(N)<cMax) and (Blue(N)<cMax);
20+ end;
2021```
2122
2223### Example 2
23-
24-``` pascal
24+```
2525// by "Hansaplast" & "Alextp" from Lazarus forum
26-function IsDarkTheme: boolean;
27- function _Level(C: TColor): double;
28- begin
29- Result:= Red(C)*0.3 + Green(C)*0.59 + Blue(C)*0.11;
30- end;
31-begin
32- Result:= _Level(ColorToRGB(clWindow)) < _Level(ColorToRGB(clWindowText));
33-end;
26+ function IsDarkTheme: boolean;
27+ function _Level(C: TColor): double;
28+ begin
29+ Result:= Red(C)*0.3 + Green(C)*0.59 + Blue(C)*0.11;
30+ end;
31+ begin
32+ Result:= _Level(ColorToRGB(clWindow)) < _Level(ColorToRGB(clWindowText));
33+ end;
3434```
3535
3636## Windows method
3737
3838### Example 1
39-
40-``` pascal
39+```
4140// by "jwdietrich" from Lazarus forum
42-uses
43- Windows, Win32Proc, Registry;
44-
45-// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
46-function IsDarkTheme: boolean;
47-const
48- KEYPATH = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
49- KEYNAME = 'AppsUseLightTheme';
50-var
51- LightKey: boolean;
52- Registry: TRegistry;
53-begin
54- Result := false;
55- Registry := TRegistry.Create;
56- try
57- Registry.RootKey := HKEY_CURRENT_USER;
58- if Registry.OpenKeyReadOnly(KEYPATH) then
59- begin
60- if Registry.ValueExists(KEYNAME) then
61- LightKey := Registry.ReadBool(KEYNAME)
41+ uses
42+ Windows, Win32Proc, Registry;
43+
44+ // IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
45+ function IsDarkTheme: boolean;
46+ const
47+ KEYPATH = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
48+ KEYNAME = 'AppsUseLightTheme';
49+ var
50+ LightKey: boolean;
51+ Registry: TRegistry;
52+ begin
53+ Result := false;
54+ Registry := TRegistry.Create;
55+ try
56+ Registry.RootKey := HKEY_CURRENT_USER;
57+ if Registry.OpenKeyReadOnly(KEYPATH) then
58+ begin
59+ if Registry.ValueExists(KEYNAME) then
60+ LightKey := Registry.ReadBool(KEYNAME)
61+ else
62+ LightKey := true;
63+ end
6264 else
6365 LightKey := true;
64- end
65- else
66- LightKey := true;
67- Result := not LightKey
68- finally
69- Registry.Free;
70- end;
71-end;
66+ Result := not LightKey
67+ finally
68+ Registry.Free;
69+ end;
70+ end;
7271```
7372
7473## macOS method
7574
7675### Example 1
77-
78-``` pascal
76+```
7977// by "trev" from Lazarus forum
80-
81-{ returns true, if this app runs on macOS 10.14.0 Mojave or newer }
82-function MojaveOrNewer: boolean;
83-var
84- minOsVer: NSOperatingSystemVersion;
85-begin
86- //Setup minimum version (Mojave)
87- minOsVer.majorVersion:= 10;
88- minOsVer.minorVersion:= 14;
89- minOsVer.patchVersion:= 0;
90-
91- // Check minimum version
92- if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
93- Result := True
94- else
95- Result := False;
96-end;
97-
98-{ The following two functions were suggested by Hansaplast at https://forum.lazarus.freepascal.org/index.php/topic,43111.msg304366.html }
99-
100-// Retrieve key's string value from user preferences. Result is encoded using NSStrToStr's default encoding.
101-function GetPrefString(const KeyName : string) : string;
102-begin
103- Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
104-end;
105-
106-// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
107-function IsDarkTheme: boolean;
108-begin
109- Result := false;
110- if MojaveOrNewer then
111- Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
112-end;
78+
79+ { returns true, if this app runs on macOS 10.14.0 Mojave or newer }
80+ function MojaveOrNewer: boolean;
81+ var
82+ minOsVer: NSOperatingSystemVersion;
83+ begin
84+ //Setup minimum version (Mojave)
85+ minOsVer.majorVersion:= 10;
86+ minOsVer.minorVersion:= 14;
87+ minOsVer.patchVersion:= 0;
88+
89+ // Check minimum version
90+ if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
91+ Result := True
92+ else
93+ Result := False;
94+ end;
95+
96+ { The following two functions were suggested by Hansaplast at https://forum.lazarus.freepascal.org/index.php/topic,43111.msg304366.html }
97+
98+ // Retrieve key's string value from user preferences. Result is encoded using NSStrToStr's default encoding.
99+ function GetPrefString(const KeyName : string) : string;
100+ begin
101+ Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
102+ end;
103+
104+ // IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
105+ function IsDarkTheme: boolean;
106+ begin
107+ Result := false;
108+ if MojaveOrNewer then
109+ Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
110+ end;
113111```
114112
115113#### Demo
116-
117-``` pascal
114+```
118115unit Unit1;
119-
120-{$mode objfpc}{$H+}
121-{$modeswitch objectivec1}
122-
123-interface
124-
125-uses
126- Classes, Forms, Dialogs, StdCtrls, SysUtils,
127- CocoaAll, CocoaUtils, MacOSAll;
128-
129-type
130-
131- { TForm1 }
132-
133- TForm1 = class(TForm)
134- Button1: TButton;
135- procedure Button1Click(Sender: TObject);
136- private
137-
138- public
139-
140- end;
141-
142-var
143- Form1: TForm1;
144-
145-implementation
146-
147-{$R *.lfm}
148-
149-{ TForm1 }
150-
151-{ returns true, if this app runs on macOS 10.14 Mojave or newer }
152-function MojaveOrNewer: boolean;
153-var
154- minOsVer: NSOperatingSystemVersion;
155-begin
156- // Setup minimum version (Mojave)
157- minOsVer.majorVersion:= 10;
158- minOsVer.minorVersion:= 14;
159- minOsVer.patchVersion:= 0;
160-
161- // Check minimum version
162- if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
163- Result := True
164- else
165- Result := False;
166-end;
167-
168-function GetPrefString(const KeyName : string) : string;
169-begin
170- Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
171-end;
172-
173-function IsDarkTheme: boolean;
174-begin
175- Result := false;
176- if MojaveOrNewer then
177- Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
178-end;
179-
180-procedure TForm1.Button1Click(Sender: TObject);
181-begin
182- if(IsDarkTheme) then
183- ShowMessage('Dark mode')
184- else
185- ShowMessage('Light mode');
186-end;
187-
188-end.
116+
117+ {$mode objfpc}{$H+}
118+ {$modeswitch objectivec1}
119+
120+ interface
121+
122+ uses
123+ Classes, Forms, Dialogs, StdCtrls, SysUtils,
124+ CocoaAll, CocoaUtils, MacOSAll;
125+
126+ type
127+
128+ { TForm1 }
129+
130+ TForm1 = class(TForm)
131+ Button1: TButton;
132+ procedure Button1Click(Sender: TObject);
133+ private
134+
135+ public
136+
137+ end;
138+
139+ var
140+ Form1: TForm1;
141+
142+ implementation
143+
144+ {$R *.lfm}
145+
146+ { TForm1 }
147+
148+ { returns true, if this app runs on macOS 10.14 Mojave or newer }
149+ function MojaveOrNewer: boolean;
150+ var
151+ minOsVer: NSOperatingSystemVersion;
152+ begin
153+ // Setup minimum version (Mojave)
154+ minOsVer.majorVersion:= 10;
155+ minOsVer.minorVersion:= 14;
156+ minOsVer.patchVersion:= 0;
157+
158+ // Check minimum version
159+ if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
160+ Result := True
161+ else
162+ Result := False;
163+ end;
164+
165+ function GetPrefString(const KeyName : string) : string;
166+ begin
167+ Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
168+ end;
169+
170+ function IsDarkTheme: boolean;
171+ begin
172+ Result := false;
173+ if MojaveOrNewer then
174+ Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
175+ end;
176+
177+ procedure TForm1.Button1Click(Sender: TObject);
178+ begin
179+ if(IsDarkTheme) then
180+ ShowMessage('Dark mode')
181+ else
182+ ShowMessage('Light mode');
183+ end;
184+
185+ end.
189186```
190187
191188### Example 2
192189
193-Catalina added an "Auto" option where the computer switches between Light and Dark modes depending on time of day. The 'AppleInterfaceStyle' method apparently doesn't work if auto is enabled.
194-
195-The solution is to get the NSApp.effectiveAppearance string which will be one of a number of values including 'NSAppearanceNameAqua' (standard light mode) and 'NSAppearanceNameDarkAqua' (standard dark mode). Google these to see the whole list which includes other light and dark modes with added contrast. The effectiveAppearance is correct in all modes including when auto mode kicks in.
190+Catalina added an "Auto" option where the computer switches between Light and Dark modes depending on time of day. The 'AppleInterfaceStyle' method apparently doesn't work if auto is enabled.
196191
197-Tested on Mojave, Catalina, Big Sur.
192+The solution is to get the NSApp.effectiveAppearance string which will be one of a number of values including 'NSAppearanceNameAqua' (standard light mode) and 'NSAppearanceNameDarkAqua' (standard dark mode). Google these to see the whole list which includes other light and dark modes with added contrast. The effectiveAppearance is correct in all modes including when auto mode kicks in.
198193
199-``` pascal
194+Tested on Mojave, Catalina, Big Sur.
195+```
200196// by "Clover" from Lazarus forum
201-function IsMacDarkMode: Boolean;
202-var
203- sMode: string;
204-begin
205- //sMode := CFStringToStr( CFStringRef( NSUserDefaults.StandardUserDefaults.stringForKey( NSSTR('AppleInterfaceStyle') ))); // Doesn't work in auto mode
206- sMode := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
207- Result := Pos('Dark', sMode) > 0;
208-end;
197+ function IsMacDarkMode: Boolean;
198+ var
199+ sMode: string;
200+ begin
201+ //sMode := CFStringToStr( CFStringRef( NSUserDefaults.StandardUserDefaults.stringForKey( NSSTR('AppleInterfaceStyle') ))); // Doesn't work in auto mode
202+ sMode := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
203+ Result := Pos('Dark', sMode) > 0;
204+ end;
209205```
210206
211207#### Demo
212-
213-``` pascal
208+```
214209unit Unit1;
215-
216-{$mode objfpc}{$H+}
217-{$modeswitch objectivec1}
218-
219-interface
220-
221-uses
222- Classes, Forms, Dialogs, StdCtrls,
223- CocoaAll, CocoaUtils, MacOSAll;
224-
225-type
226-
227- { TForm1 }
228-
229- TForm1 = class(TForm)
230- Button1: TButton;
231- procedure Button1Click(Sender: TObject);
232- private
233-
234- public
235-
236- end;
237-
238-var
239- Form1: TForm1;
210+
211+ {$mode objfpc}{$H+}
212+ {$modeswitch objectivec1}
213+
214+ interface
215+
216+ uses
217+ Classes, Forms, Dialogs, StdCtrls,
218+ CocoaAll, CocoaUtils, MacOSAll;
219+
220+ type
221+
222+ { TForm1 }
223+
224+ TForm1 = class(TForm)
225+ Button1: TButton;
226+ procedure Button1Click(Sender: TObject);
227+ private
228+
229+ public
230+
231+ end;
232+
233+ var
234+ Form1: TForm1;
235+
236+ implementation
237+
238+ {$R *.lfm}
239+
240+ { TForm1 }
241+
242+ function IsMacDarkMode: Boolean;
243+ var
244+ sMode: string;
245+ begin
246+ sMode := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
247+ Result := Pos('Dark', sMode) > 0;
248+ end;
249+
250+ procedure TForm1.Button1Click(Sender: TObject);
251+ begin
252+ if(IsMacDarkMode) then
253+ ShowMessage('Dark mode')
254+ else
255+ ShowMessage('Light mode');
256+ end;
257+
258+ end.
259+```
240260
241-implementation
261+### Opting out of dark mode
242262
243-{$R *.lfm}
263+Mac systems automatically opt in any application linked against the macOS 10.14 or later SDK to both light and dark appearances. You can opt out of dark mode by including the NSRequiresAquaSystemAppearance key (with a value of YES) in your application’s [[macOS property list files|Info.plist]] property list file. Setting this key to YES causes the system to ignore the user's preference and always apply a light appearance to your application.
244264
245-{ TForm1 }
265+### Opting in to dark mode
246266
247-function IsMacDarkMode: Boolean;
248-var
249- sMode: string;
250-begin
251- sMode := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
252- Result := Pos('Dark', sMode) > 0;
253-end;
267+If you build your application against an earlier SDK but still want to support Dark Mode, include the NSRequiresAquaSystemAppearance key (with a value of NO) in your application's [[macOS property list files|Info.plist]] property list file. Do so only if your application's appearance looks correct when running in macOS 10.14 and later with Dark Mode enabled.
254268
255-procedure TForm1.Button1Click(Sender: TObject);
256-begin
257- if(IsMacDarkMode) then
258- ShowMessage('Dark mode')
259- else
260- ShowMessage('Light mode');
261-end;
269+# Dark Windows title bar
262270
263-end.
271+For Windows 10 and higher versions it is possible to activate dark title bar of the application window.
264272```
273+uses LCLType;
274+
275+ function IsWindows10OrGreater(BuildNumber: Integer): Boolean;
276+ begin
277+ Result := (Win32MajorVersion >= 10) and (Win32BuildNumber >= BuildNumber);
278+ end;
279+
280+ procedure SetDarkModeTitleBar(AForm: TForm; Active: Bool);
281+ type
282+ TDwmSetWindowAttribute = function(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HRESULT; stdcall;
283+ const
284+ DwmapiLibName = 'dwmapi.dll';
285+ DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
286+ DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
287+ var
288+ DwmapiLib: TLibHandle;
289+ DwmSetWindowAttribute: TDwmSetWindowAttribute;
290+ Attr: DWord;
291+ begin
292+ DwmapiLib := LoadLibrary(DwmapiLibName);
293+ if DwmapiLib <> 0 then DwmSetWindowAttribute := GetProcAddress(DwmapiLib, 'DwmSetWindowAttribute')
294+ else DwmSetWindowAttribute := nil;
295+
296+ if Assigned(DwmSetWindowAttribute) and IsWindows10OrGreater(17763) then begin
297+ Attr := DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;
298+ if IsWindows10OrGreater(18985) then Attr := DWMWA_USE_IMMERSIVE_DARK_MODE;
299+
300+ DwmSetWindowAttribute(AForm.Handle, Attr, @Active, SizeOf(Active));
301+ end;
302+ if DwmapiLib <> 0 then FreeLibrary(DwmapiLib);
303+ end;
304+```
265305
266-### Opting out of dark mode
306+# Activate Dark Theme
267307
268-Mac systems automatically opt in any application linked against the macOS 10.14 or later SDK to both light and dark appearances. You can opt out of dark mode by including the NSRequiresAquaSystemAppearance key (with a value of YES) in your application’s [[macOS property list files|Info.plist]] property list file. Setting this key to YES causes the system to ignore the user's preference and always apply a light appearance to your application.
308+## Windows IDE
269309
270-### Opting in to dark mode
310+To make the Lazarus IDE in dark themed, you can use the package **metadarkstyledsgn.lpk**. At the time of writing 17th Oct 2024 the version in the _online package manager_ is broken. Use the git version [metadarkstyle](<https://github.com/zamtmn/metadarkstyle>).
271311
272-If you build your application against an earlier SDK but still want to support Dark Mode, include the NSRequiresAquaSystemAppearance key (with a value of NO) in your application's [[macOS property list files|Info.plist]] property list file. Do so only if your application's appearance looks correct when running in macOS 10.14 and later with Dark Mode enabled.
312+## MacOS
273313
274-## See Also
314+ * On Intel Macs you need to compile with _-WM10.14_ to support dark mode. Better use **-WM11** to also support modern style form.
315+ * For the IDE: add it to the Tools / Configure "Build Lazarus" / Options
316+ * For a project: add it to Project / Project Options / Compiler Options / Custom Options
275317
276-- [[macOS extensions]]
318+# See Also
277319
278-### External links
320+ * [[macOS extensions]]
321+ * [[UserSuppliedSchemeSettings|User provided syntax highlighting - color schemes]]
279322
280-- [Apple: Mojave Dark Mode](https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/).
323+## External links
281324
325+ * [Apple: Mojave Dark Mode](<https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/>).
326+
282327---
283-*From the Free Pascal wiki, page "Dark theme", as it stood on 2021-01-31 after a change by Jwdietrich, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
284-Category: Code Snippets
285-Category: FPC
286-Category: Platform-sensitive development
287-Category: Windows
288-Category: macOS
328+*From the Free Pascal wiki, page "Dark theme", as the Internet Archive kept it on 2025-01-20, last changed on 2024-12-11. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history