Starting with macOS (10.16 "Beta"/11.0) "Big Sur", the menu-bar and system tray no longer honor the Desktop dark-mode preference, making it difficult to properly theme a system tray icon for this Desktop.
Previously, using a shell command default read
, the dark mode could be detected:
defaults read -g AppleInterfaceStyle
# "Dark"
This still works great for detecting the Window theme, but it does not work for the menu-bar and system tray theme.
Since this area seems to be driven by the wallpaper brightness/whiteness/luminosity, how do we detect a dark system tray?
How to detect this in (e.g.) Objective-C/C++? Any solution is welcome, as most can be adapted.
Question also posted to Apple Developer forums: https://developer.apple.com/forums/thread/652540
Qt5.6 has a feature called setIsMask(...)
which allows the OS to handle this automatically. This is effectively an alias for NSImage::setTemplate:Yes
More references to macOS "Dark Mode":
AdoptOpenJDK upstream bug report:
Keywords: NSStatusBar
, Menu Bar Extras
Luckily, reverting to the traditional opaque menu bar is simple. Open System Preferences > Accessibility > Display and select Reduce Transparency.
Settings > General Dark mode. Then, go to Accessibilty > Reduce Transparency. Then, just like in full screen where it turns the area left/right of the notch pitch black, the menu bar will be pitch black also, and the notch vanishes. This menu bar with mini LED will be as black as the bezel.
You need to Option-click on the time display and it'll change to a higher contrast color choice. Option-click again and it'll go back to “Big Sur default”.
What Does System Tray (Systray) Mean? The system tray is a notification area on the operating system taskbar. It contains icons that provide users with easy access to system functions such as email, printer, network connections and volume control.
I had the same issue but I think I found a solution. As written in AppKit Release Notes for Big Sur (see the entry for NSStatusItem
) you can just observe NSStatusItem
's button
's effectiveAppearance
. If the name of the effectiveAppearance
contains dark
, then it's the dark mode. Otherwise it's the light mode.
A sample code I created which shows light
or dark
as the text label of the NSStatusItem
is available at this GitHub repo, see in particular AppDelegate.m. (I'm sorry for being a near-extinct dinosaur for using Objective-C.) You can test it by running on Catalina or on Big Sur, changing the Dark/Light settings or the color of the desktop picture from System Preferences.
Edit: It turns out that Big Sur sometimes changes effectiveAppearance
from light to light or from dark to dark (in the sense that KVO is invoked although the appearance hasn’t actually changed.) Therefore it’s advisable to check both the value of effectiveApparance
before and after the change to confirm the value actually changed.
I just submitted a TSI and I got an answer:
But I would not add an NSView on top of the NSStatusItem’s button directly. The docs mention to use the button property to customize the appearance and behavior of the status item, but it should work within the confines of the button itself, that is, it’s various properties like the image and text and their placements. Years ago, NSStatusItem allowed for custom views, but then became deprecated, in order to support a button-based UI, therefore allowing its drawing behavior to easily adapt to changes in the menu bar’s appearance.
So unfortunately there is no way to get this information programatically. However, getting information is very important for three of my apps, so I went exploring.
For me personally, it was very important that getting this information will not trigger any security prompts.
I came up with the following idea:
NSStatusItem
NSImage
CALayer
into a NSImage
You can use this code to get the colour information (note: the NSStatusItem
is never visible and does not cause existing items to move or something like that). Feel free to adjust the formatting and classes:
I have created a class called MenuBar
with a public property:
public class MenuBar {
private static var statusItem: NSStatusItem?
public static var theme: MenuBarTheme {
if self.statusItem == nil {
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
self.statusItem?.button?.image = NSImage(systemSymbolName: "circle.fill", accessibilityDescription: nil)
self.statusItem?.isVisible = false
}
if let color = self.getPixelColor() {
return color.redComponent < 0.20 && color.blueComponent < 0.20 && color.greenComponent < 0.20 ? .light : .dark
}
else
{
return NSApplication.isDarkMode ? .dark : .light
}
}
public static var tintColor: NSColor {
return self.theme == .light ? NSColor.black : NSColor.white
}
// MARK: - Helper
fileprivate static func getPixelColor() -> NSColor?
{
if let image = self.statusItem?.button?.layer?.getBitmapImage() {
let imageRep = NSBitmapImageRep(data: image.tiffRepresentation!)
if let color = imageRep?.colorAt(x: Int(image.size.width / 2.0), y: Int(image.size.height / 2.0)) {
return color
}
}
return nil
}
}
public enum MenuBarTheme : String
{
case light = "light"
case dark = "dark"
}
public extension NSApplication
{
class var isDarkMode: Bool
{
return NSApplication.shared.appearance?.description.lowercased().contains("dark") ?? false
}
}
public extension CALayer
{
func getBitmapImage() -> NSImage
{
let btmpImgRep = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(self.frame.width), pixelsHigh: Int(self.frame.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 32)
let ctx = NSGraphicsContext(bitmapImageRep: btmpImgRep!)
let cgContext = ctx!.cgContext
self.render(in: cgContext)
let cgImage = cgContext.makeImage()
return NSImage(cgImage: cgImage!, size: CGSize(width: self.frame.width, height: self.frame.height))
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With