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))
}
}