Notes on adding support for Dark Mode

This week I added support for macOS Mojave’s new Dark Mode to the GUI apps in Munki: https://github.com/munki/munki/commit/dea412d72e277265bc98dea2b358be5a830739b6

I initially thought adding Dark Mode support would be problematic. Most of the documentation and advice I’d seen seemed to imply that you had to build against the 10.14 SDK to get support for Dark Mode.

This was problematic for a couple of reasons: 1) To build against the 10.14 SDK, you need to use (the beta) Xcode 10. Often in the past, moving to a new version of Xcode meant lots of annoying little adjustments to the project. But more importantly: 2) Xcode 10 appears to have dropped support for building non-signed applications. I’m not interested in signing Munki’s GUI apps with my own personal Developer ID; I think my company would not want their ID attached to the apps, and I don’t have the bandwidth or interest in figuring out how to set up some sort of organization that could be the signing organization, and then figure out how to securely share the needed keys, etc, etc.

But it turns out you don’t need to link against the 10.14 SDK at all.

Opting in to Dark Mode

https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_app?language=objc

Apps linked against macOS 10.14 or later should support both light and dark appearances. If you build your app against an earlier SDK, you can still enable Dark Mode support by including the NSRequiresAquaSystemAppearance key (with a value of NO) in your app’s Info.plist file.

All you need to do to opt-in to Dark Mode is to add a key and value to your application’s Info.plist. If your app uses only native controls and views, that might be all you need to do. For the MunkiStatus application, that _was_ all I did. The status window and log window are all constructed from standard widgets, and Apple’s frameworks do all the work of enabling Dark Mode when the user chooses that appearance.

Managed Software Center uses the same log window as MunkiStatus, so that’s covered. The toolbar and other window chrome in Managed Software Center’s main window are also standard GUI elements, so they automatically get Dark Mode appearance. But this same main window is dominated by a WebView — most of the UI is actually built using HTML, CSS, and JavaScript. In order to look good in Dark Mode, we’ll need to adjust the CSS to provide a dark appearance.

This meant I needed a few things:

  1. A way to detect the currently chosen appearance mode (Dark or “Light”)
  2. A way to be notified when the user changes appearances
  3. Some way to update the CSS in use to change the WebView’s appearance to match the overall macOS UI appearance

Getting the currently selected mode

Solutions for the first two needs are not well-documented by Apple. For the first, there’s an NSAppearance API that has been extended in Mojave to provide a way to get the currently selected appearance, but when I tried to use it in a recent Mojave beta I got the same “NSAppearanceNameAqua” back no matter if I had chosen Dark Mode or Light Mode. (I was expecting to get “NSAppearanceNameDarkAqua” in Mojave when Dark Mode was active). This might be a bug, or I might have been doing something wrong. In any case, I found an alternative that works for my needs so far.

I found several references to reading a “AppleInterfaceStyle” preference like this one:

https://stackoverflow.com/questions/25207077/how-to-detect-if-os-x-is-in-dark-mode

and a PyObjC variation of

NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];

seemed to do the trick as far as querying the currently selected interface mode/style. I can find zero Apple documentation on this, so if I eventually do figure out how to get the info I need from NSAppearance I’ll switch to that, since that method/API does seem to be supported by Apple.

Getting notifications of interface style changes

For the second need, searching turned up this:

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

and code like this:

[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(darkModeChanged:) name:@"AppleInterfaceThemeChangedNotification" object:nil];

-(void)darkModeChanged:(NSNotification *)notif {
NSLog(@"Dark mode changed");
}

This was pretty easy to implement, and I got a notification whenever I changed the interface mode in System Preferences->General.

Oddly, a search for “AppleInterfaceThemeChangedNotification” turns up nothing in Apple’s developer site search.

Updating CSS

Of course this biggest and most important part of adding support for Dark Mode in Managed Software Center was making the CSS changes. For that, I am greatly indebted to this article:

View story at Medium.com

In this article I learned about CSS “variables” and I liberally adapted Marcin’s techniques for adding support for interface styles to CSS and his JavaScript for doing a cross-fade transition between the styles when the mode is changed.

Most of the non-text stuff looked acceptable in Dark Mode with no changes. Mostly I needed to define CSS “variables” for various emphasis weights for text colors and some button backgrounds. Using Safari’s Web Inspector made figuring out which CSS bit affected the display style of a given interface element much much easier. (Managed Software Center generates HTML pages that can be opened in Safari or other web browsers — you can then use the browser’s developer tools to inspect the html, css, and javascript. You can even tweak the CSS values “live” to quickly test your proposed changes.)

All that was left was several days of trying CSS changes, building the app and playing with it, finding more items that needed to be tweaked, and repeating the cycle until I was relatively happy with the look in Dark Mode.

I hope these notes are helpful to others looking to add Dark Mode support to their apps.

Advertisements
Notes on adding support for Dark Mode

One thought on “Notes on adding support for Dark Mode

Comments are closed.