macOS“Big Sur”检测深色菜单栏/系统托盘

Posted

技术标签:

【中文标题】macOS“Big Sur”检测深色菜单栏/系统托盘【英文标题】:macOS "Big Sur" Detect dark menu-bar/system tray 【发布时间】:2020-10-22 10:32:23 【问题描述】:

从 macOS (10.16 "Beta"/11.0) "Big Sur" 开始,菜单栏和系统托盘不再支持桌面暗模式首选项,因此很难为该桌面正确设置系统托盘图标的主题。

以前,使用 shell 命令default read,可以检测到暗模式:

defaults read -g AppleInterfaceStyle
# "Dark"

这仍然适用于检测 Window 主题,但不适用于菜单栏和系统托盘主题。

由于这个区域似乎是由壁纸亮度/白度/亮度驱动的,我们如何检测黑暗的系统托盘?

如何在(例如)Objective-C/C++ 中检测到这一点?欢迎任何解决方案,因为大多数都可以调整。

问题也发布到 Apple 开发者论坛:https://developer.apple.com/forums/thread/652540

Qt5.6 有一个名为setIsMask(...) 的功能,它允许操作系统自动处理这个问题。这实际上是 NSImage::setTemplate:Yes 的别名

更多参考 macOS “黑暗模式”:

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

AdoptOpenJDK 上游错误报告:

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

关键字:NSStatusBarMenu Bar Extras

【问题讨论】:

我也对解决方案很感兴趣。 @inexcitus 我们已经放弃从 Apple 获得帮助并开始重写我们的应用程序以使用 NSImage:isTemplate。这是不幸的,因为它需要对我们正在使用的框架进行补丁。在我们的例子中,我们寄希望于未来的 Java 版本可以本地处理这个问题。如果您还使用框架来绘制图标,您可以使用我们的代码作为您正在使用的任何框架的功能请求中的参考点:github.com/AdoptOpenJDK/openjdk-support/issues/…。如果你的应用是原生 C++ 或 Objective-C 应用,你应该考虑直接使用NSImage:isTemplate * 更正,API是NSImage:setTemplate(true)(不是isTemplate)。 > NSView with text 我们不得不将代码切换为使用按钮(技术上是实现NSMenuDelegateNSObject)。随意使用尽可能多的代码。 github.com/AdoptOpenJDK/openjdk-jdk11u/compare/…。除了建议您使用不同的 API 之外,我认为 Apple 无法提供帮助。 @inexcitus 还注意到我们选择在 Java diff 中使用的 API 是 10.5+,所以它们已经存在了大约 13 年,应该不会导致回归。 【参考方案1】:

我遇到了同样的问题,但我想我找到了解决方案。正如AppKit Release Notes for Big Sur 中所写(请参阅NSStatusItem 的条目),您只需观察NSStatusItembuttoneffectiveAppearance。如果effectiveAppearance 的名称中包含dark,则为暗模式。否则就是灯光模式。

我创建的将lightdark 显示为NSStatusItem 的文本标签的示例代码可在this GitHub repo 获得,具体参见AppDelegate.m。 (我很抱歉因为使用 Objective-C 而成为近乎灭绝的恐龙。)您可以通过在 Catalina 或 Big Sur 上运行来测试它,从系统偏好设置中更改暗/亮设置或桌面图片的颜色。

编辑:事实证明,Big Sur 有时变化 effectiveAppearance 从亮到亮或从暗到暗(从某种意义上说,虽然外观实际上并没有改变,但调用了 KVO。)因此,建议在更改前后检查effectiveApparance 的值,以确认实际更改的值。

【讨论】:

比我的解决方案容易得多? 恐龙很酷,Objective-C 也很酷。 ?? 这在 Big Sur 上使用 Light 模式但背景图像是黑色的情况下是否有效,将菜单栏渲染为黑色?或者反之亦然,在深色模式下使用浅色背景图像? 是的。要查看它,请从上面链接的 GitHub 存储库下载测试应用程序。【参考方案2】:

我刚刚提交了一份 TSI 并得到了答复:

但我不会在 NSStatusItem 的按钮上添加 NSView 直接地。文档提到使用按钮属性进行自定义 状态项的外观和行为,但它应该可以工作 在按钮本身的范围内,也就是说,它是各种 图像和文本及其位置等属性。几年前, NSStatusItem 允许自定义视图,但随后被弃用,在 为了支持基于按钮的 UI,因此允许其绘图 行为以轻松适应菜单栏外观的变化。

不幸的是,没有办法以编程方式获取这些信息。 但是,获取信息对于我的三个应用程序来说非常重要,所以我去探索了。

对我个人而言,获取此信息不会触发任何安全提示非常重要。

我想出了以下想法:

创建和隐藏NSStatusItem 设置模板NSImageCALayer 的内容渲染成NSImage

您可以使用此代码获取颜色信息(注意:NSStatusItem 永远不可见,并且不会导致现有项目移动或类似的事情)。随意调整格式和类:

我创建了一个名为 MenuBar 的类,它有一个公共属性:

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

【讨论】:

【参考方案3】:

对于 Java 17,已添加此功能。它最终将被向后移植到 Java 11。

https://github.com/openjdk/jdk/pull/481

新属性是:

apple.awt.enableTemplateImages

从命令行:

-Dapple.awt.enableTemplateImages=true

...或通过 Java:

System.setProperty("apple.awt.enableTemplateImages", "true");

注意,盯着 Big Sur 看,图标不再是纯黑色或纯白色(而是略带阴影),因此使用模板 API 对于正确的外观和感觉很重要。

【讨论】:

【参考方案4】:

虽然较新的 JDK 版本 will have this setTemplate feature natively,但对于较旧的 Java 版本,此技术使用 JNA 并依赖于 https://github.com/dyorgio/macos-tray-icon-fixer 项目。在内部,它使用 Java 反射和 JNA 指针的组合来更新图像并设置setTemplate flag to true。

已经用JDK8和JDK11测试过了。如果使用带有aarch64 support 的 JNA 端口,它也可以与新的 Apple Silicon Mac 一起使用。

感谢@dyorgio 提供的技巧。

// Verify theme to choose image version
Image iconImage = MacOSTrayIconFixer.getInitialIcon(blackImage, whiteImage);
// Create your TrayIcon using image
TrayIcon icon = new TrayIcon(iconImage, "Test");
// (Optional) add action and menu
icon.addActionListener((e) -> System.out.println("examples.BasicUsage.main():" + SwingUtilities.isEventDispatchThread()));

// Include in SystemTray BEFORE call fixer
SystemTray.getSystemTray().add(icon);

// Fix
MacOSTrayIconFixer.fix(icon, blackImage, whiteImage, false, 0);

【讨论】:

以上是关于macOS“Big Sur”检测深色菜单栏/系统托盘的主要内容,如果未能解决你的问题,请参考以下文章

macOS Big Sur 发布!全新界面更像 iOS

macOS Big Sur发布:设计重塑 有史以来最大更新

聊聊 macOS Big Sur

macOS Big Sur DP1v11.0测试版下载安装

试试macOS 11 Big Sur系统?附镜像和虚拟机图文配置教程

macOS Big Sur发布,macOS迎来最大升级