Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

macOS "Big Sur" Detect dark menu-bar/system tray

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?

n

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":

  • How to detect if OS X is in dark mode?
  • MenuBar Icon for Dark Mode on OS X in Java

AdoptOpenJDK upstream bug report:

  • https://github.com/AdoptOpenJDK/openjdk-support/issues/146

Keywords: NSStatusBar, Menu Bar Extras

like image 755
tresf Avatar asked Jul 01 '20 21:07

tresf


People also ask

How do I make my Big Sur Mac menu bar dark?

Luckily, reverting to the traditional opaque menu bar is simple. Open System Preferences > Accessibility > Display and select Reduce Transparency.

How do I get the black menu bar on my Mac?

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.

Can I fix or change the menu bar clock color in macOS Big Sur?

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 is System Tray area on Mac?

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.


Video Answer


2 Answers

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.

like image 56
Yuji Avatar answered Oct 14 '22 19:10

Yuji


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:

  • Create and hide a NSStatusItem
  • Set a templated NSImage
  • Render the contents of the 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))
    }
}
like image 32
inexcitus Avatar answered Oct 14 '22 20:10

inexcitus