How to detect if macOS changed Theme (Dark vs Light) while running? lazarus / 28-Cocoa ---------------------------------------------------------------------- Hansaplast · 2018-11-05 15:12 The latest SVN version (r59451) seems to include some impressive work on support for Dark and Light Theme under Mojave. When switching theme, the IDE changes as well. This even works with my own applications now! Awesome!!  I was wondering if there is an event that I can tie a procedure to so I can have my application respond to the color changes (for custom drawn controls etc)? ---------------------------------------------------------------------- CCRDude · 2018-11-05 16:15 I would like to second that request Or simply a way to query as a first step. ---------------------------------------------------------------------- Hansaplast · 2018-11-05 16:29 Hi CCRDude! I found a way to query if Dark Theme is active or not, even if it's not the most elegant way, but it's fast and it works. The preferences value updates right away when a user switched back and forth between dark and light theme. You can glue this in one function of course, I just use the GetPrefString function for other purposes as well. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i // Retrieve key's string value from user preferences. Result is encoded using NSStrToStr's default encoding.function GetPrefString(const KeyName : string) : string;begin  Result := NSStringToString(NSUserDefaults.stan dardUserDefaults.stringForKey(NSStr(@KeyName[1])));end;  // IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)function IsDarkTheme:boolean;begin  Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle')))>0;end;    Note: I got the function to get the preference keyword from one of Phil's units if I recall correctly - there are some very neat and useful tools in his units. The biggest problem I run into is that I need to update other controls as well to match the new colors, but have no means to get alerted about the change ---------------------------------------------------------------------- Hansaplast · 2018-11-09 14:28 Using the function above, I did find a way to detect the change by using the onPaint event. However I'm not happy, or maybe I should say "concerned", with how often the onPaint event is fired, especially when resizing a form. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.FormPaint(Sender: TObject);begin  if IsDarkTheme then    label1.Caption:='Dark Theme'  else    label1.Caption:='Light Theme';end; I'd rather see a specific event, based on theme change. I've tried looking at something like this (usign addObserver, with themeChanged() being the notification function); Code: C  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i [NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];  -(void)themeChanged:(NSNotification *) notification {    NSLog (@"%@", notification);} But I have not been successful in translating this to Pascal, even with Dmitry's help who suggested looking at (in cocoawsforms) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i NSNotificat ionCenter.defaultCenter.addObserver_selector_name_object(cnt, objcselector('didResignKeyNotification:'), NSWindowDidResignKeyNotification, cnt.window); Any help would be much appreciated. ---------------------------------------------------------------------- jwdietrich · 2018-11-25 22:19 For me, it works great. The onPaint event seems to be automatically fired every time the theme is changed. ---------------------------------------------------------------------- Hansaplast · 2018-11-26 09:53 It works fine indeed, just the when resizing a form this event gets fired tons of times - that was my only concern. You can try with a simple example; add a TLabel to your form, and do the following on the onPaint event: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   Label1.Tag := Label1.Tag+1;  Label1.Caption:=IntToStr(Label1.Tag); Now run you application and resize the window ... On top of that; Cocoa has a function to have the OS inform your application that the theme has changed - which would be only one single call when the theme changes. I have not been able to get that call to work with lazarus though, due to my lack of experience with converting Obj-C calls. Disk Arbitration has a similar function (called in a different way) to let you application know if a disk was inserted, ejected, mounted or unmounted as well, and that one I did get to work. ---------------------------------------------------------------------- lucamar · 2018-11-26 13:20 Hansaplast, I think at least part of your problem is that if you set the caption in the OnPaint handler it will trigger another repaint and another call to OnPaint. ---------------------------------------------------------------------- Hansaplast · 2018-11-26 13:36 Oh duh! Good call lucamar!  I'll test it without changing the label and see what happens. ---------------------------------------------------------------------- Hansaplast · 2018-11-26 13:53 Quote from: lucamar on November 26, 2018, 02:20:22 pmHansaplast, I think at least part of your problem is that if you set the caption in the OnPaint handler it will trigger another repaint and another call to OnPaint. I did a simple test, and even though it's not 100% accurate, it seems that changing the caption does not trigger the onPaint event of the form itself (only a repaint of TLabel?). Simple resizing of the form to a little bigger, triggered onPaint around 20 times, without updating the TLabel Caption. Doing the same test, however with updating the TLabel caption, triggered it around 20 times as well. Since it's kinda hard to measure, it all depends how steady my hand is and if I have the mouse travel the exact same direction and distance at the same speed, I'm guessing onPaint triggers about the same amount of times during a resize, with ot without changing TLabel.caption. I tried playing with TForm.IsResizing, but that doesn't seem to be doing anything (ie. this value doesn't appear to be "true" at any time while resizing). Example code I used - simple form, with a TLabel on it: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Unit1;{$mode objfpc}{$H+}interfaceuses  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;type  { TForm1 }  TForm1 = class(TForm)    Label1: TLabel;    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);    procedure FormCreate(Sender: TObject);    procedure FormPaint(Sender: TObject);  private    counter:integer;  public  end;var  Form1: TForm1;implementation{$R *.lfm}{ TForm1 }procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);begin  ShowMessage(IntToStr(counter)); // show countend;procedure TForm1.FormCreate(Sender: TObject);begin  Counter:=0;end;procedure TForm1.FormPaint(Sender: TObject);begin  inc(Counter);  //label1.Caption:=IntToStr(counter);end;end. ---------------------------------------------------------------------- VTwin · 2018-12-09 14:16 I notify forms of events, such as changes in file or item selections by sending a message: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  TVMessage = packed record    Id    : longint;    Param : word;    Flag  : word;    Text  : shortstring;  end; { DispatchMessage  Dispatches a message to all forms. This adds a message to the message queue,  which will be processsed by the handler after the current process finishes.  Define a message id such as:    LM_Msg = LM_USER + 1;  where LM_USER is defined in LMessages, and a handler such as:    procedure OnMessage(var msg: TLMessage); message LM_Msg; }procedure DispatchMessage(Id: dword; Param, Flag: word; Txt: string);var  i: integer;  f: TForm;  m: TVMessage;begin  m.Id := Id;  m.Param := Param;  m.Flag := Flag;  m.Text := Txt;  for i := 0 to Screen.FormCount - 1 do begin    f := Screen.Forms[i];    if (f <> nil) then      f.Dispatch(m);  end;end;  In my case, when the user selects a data item an image in another form is updated. Of course this could be modified for objects other than forms. You could test for a change in dark theme in a timer, then dispatch a message. Any forms with custom controls could react as needed. Just a thought, there is probably a better way. Cheers, VTwin ---------------------------------------------------------------------- Hansaplast · 2018-12-09 16:08 Thanks VTwin! I'll give that a try. Not crazy about using a timer though, but I'll give it a try. OnPaint does seem to work OK and not bog down the application too much. ---------------------------------------------------------------------- VTwin · 2018-12-09 16:37 Quote from: Hansaplast on December 09, 2018, 05:08:02 pmNot crazy about using a timer though, but I'll give it a try. I try not to use them too much, but they can be handy. I had been using timers to set and check global flags for changes requiring updates, but that started getting messy and I mostly switched to messages. I assume people don't switch themes very often, so maybe a small lag is not a big deal? If the timer checked once every 1000 ms (or more) perhaps that is enough. A simpler option than messages is to just set a global flag, "gIsDarkTheme" in the timer. The controls would only get updated when a repaint is triggered, but maybe that is sufficient, and is similar to what you are doing now. ---------------------------------------------------------------------- Clover · 2020-08-26 18:46 The 'AppleInterfaceStyle' method doesn't work in "Auto" mode (introduced in Catalina), which switches between Light and Dark depending on time of day. The following updated test works correctly on Mojave, Catalina and Big Sur: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function isMacDarkMode: Boolean;var sMode: string;begin  //sMode  := CFStringToStr( CFStringRef( NSUserDefaults.StandardUserDefaults.stringForKey( NSSTR('AppleInterfaceStyle') ))); // Doesn't work in auto mode  sMode  := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));  Result := Pos('Dark', sMode) > 0;end; ---------------------------------------------------------------------- VTwin · 2020-08-30 15:08 Quote from: Clover on August 26, 2020, 08:46:56 pmThe 'AppleInterfaceStyle' method doesn't work in "Auto" mode (introduced in Catalina), which switches between Light and Dark depending on time of day. The following updated test works correctly on Mojave, Catalina and Big Sur: ... Thanks! Can you give the required uses files? ---------------------------------------------------------------------- trev · 2020-08-31 00:24 @VTwin: See the Wiki article for a simple demo I wrote. ---------------------------------------------------------------------- Hansaplast · 2020-08-31 09:27 Not sure how others do this, but I check for Dark Theme during the Form's onPaint event - so the application can change certain things (for example controls that do not respond well to a theme change, or controls that use non-standard colors). This works for all my apps just fine. Well, except for Windows, since Lazarus doesn't change colors when you change theme in Windows. If this is the correct way to do this; maybe we can add this as a tip to the Wiki page? ---------------------------------------------------------------------- trev · 2020-09-01 02:18 Quote from: Hansaplast on August 31, 2020, 11:27:28 amIf this is the correct way to do this; maybe we can add this as a tip to the Wiki page? I think it's a hack You mentioned the "correct" method in your earlier 2018 post in this thread... except you didn't manage to get it working It's beyond me too... In which case, a hack may be better than nothing ---------------------------------------------------------------------- Wallaby · 2022-11-23 23:32 I know it's an old thread, but I faced the same problem. I needed to adjust certain custom colours when macOS switches to Dark or Light theme. Here is the code that will receive the event whenever the theme is changed, manually or automatically: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit Core.MacOS.DarkMode; {$MODESWITCH OBJECTIVEC1} interface uses  Classes, SysUtils, CocoaAll, MacOsAll; implementation type  TThemeChangedNotification = objcclass(NSObject)    procedure ThemeChangedNotification(notification: NSNotification);      message 'ThemeChangedNotification:';  end; procedure TThemeChangedNotification.ThemeChangedNotification(  notification: NSNotification);begin  // This will be triggered upon theme changeend; var  ThemeChangedNotification: TThemeChangedNotification;  DistributedNotificationCenter: NSDistributedNotificationCenter; initialization  ThemeChangedNotification := TThemeChangedNotification.alloc.init;  ThemeChangedNotification.retain;  DistributedNotificationCenter := NSDistributedNotificationCenter.defaultCenter;  if Assigned(DistributedNotificationCenter) then    DistributedNotificationCenter.addObserver_selector_name_object(      ThemeChangedNotification, ObjCSelector('ThemeChangedNotification:'),    NSSTR('AppleInterfaceThemeChangedNotification'), nil); finalization  ThemeChangedNotification.release; end. ---------------------------------------------------------------------- Hansaplast · 2025-11-13 15:37 And an even later reply haha. Excellent code Wallaby! Works great for me. I used to use the Form.OnPaint event but as of recent that no longer works as hoped for. Your code on the other hand works great. Thank you! ---------------------------------------------------------------------- rich2014 · 2025-12-21 15:20 in the latest Lazarus/Cocoa version, switching between Dark and Light modes will trigger ThemeServices.OnThemeChange. additionally, if necessary, we can use TCocoaThemeServices.isDark() to check if the app is currently in dark mode. ---------------------------------------------------------------------- Hansaplast · 2025-12-22 09:21 Awesome! Thank you Rich2014!