如何在 OSX 中获取 NSStatusBar 的所有 NSStatusItem 元素?

发布于 2025-01-01 23:44:05 字数 312 浏览 1 评论 0原文

我需要获取 OSX 状态栏中的所有元素。

我试图获取系统的 NSStatusBar id: [NSStatusBar systemStatusBar] 但我不知道如何获取其中的所有 NSStatusItems。 我在 NSStatusBar 中找到了一个名为 _items 的私有方法,但我无法调用它:

[[NSStatusBar systemStatusBar] _items];

Xcode 告诉我该方法不存在。

如何获取 NSStatusBar 中的所有 NSStatusItem 元素?

谢谢

I need get all elements in the status bar in OSX.

I tried to get the NSStatusBar id of the System: [NSStatusBar systemStatusBar] but I don't know how can I get all NSStatusItems in it.
I found a private method named _items in NSStatusBar but I can't call it:

[[NSStatusBar systemStatusBar] _items];

Xcode tould me that that method doesn't exist.

How can I get all NSStatusItem elements in the NSStatusBar?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

合约呢 2025-01-08 23:44:05

您无法将所有项目作为 NSStatusItem 对象获取,因为它们并不全部属于您的进程。

如果您只对它们在屏幕上的位置以及哪些应用程序拥有它们感兴趣,则可以使用 CGWindow API 来实现,因为从技术上讲,状态项是(无边框)窗口。下面是一个记录所有状态栏项目信息的示例:

NSArray *windowInfos = (NSArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); 
for (NSDictionary *windowInfo in windowInfos) {
    if (([[windowInfo objectForKey:(id)kCGWindowLayer] intValue] == 25) 
        && (![[windowInfo objectForKey:(id)kCGWindowOwnerName] isEqual:@"SystemUIServer"])) {
        NSLog(@"Status bar item: %@", windowInfo);
    }
}
[windowInfos release];

请注意,不包括系统的项目;它们全部组合在一个属于“SystemUIServer”的窗口中。此外,此方法可能不是特别可靠,因为状态栏项目的窗口层可能会更改(此处假设为 25,但这没有在任何地方记录)。

You cannot get all items as NSStatusItem objects because they don't all belong to your process.

If you're only interested where they are on screen and which apps own them, you can do that with the CGWindow APIs, because technically the status items are (borderless) windows. Here's an example that logs information about all status bar items:

NSArray *windowInfos = (NSArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); 
for (NSDictionary *windowInfo in windowInfos) {
    if (([[windowInfo objectForKey:(id)kCGWindowLayer] intValue] == 25) 
        && (![[windowInfo objectForKey:(id)kCGWindowOwnerName] isEqual:@"SystemUIServer"])) {
        NSLog(@"Status bar item: %@", windowInfo);
    }
}
[windowInfos release];

Note that the system's items are not included; they are all combined in one window that belongs to "SystemUIServer". Also, this method might not be particularly reliable because the window layer for status bar items might change (it's assumed to be 25 here, but this is not documented anywhere).

碍人泪离人颜 2025-01-08 23:44:05

这是 Swift 中的更新片段:

import AppKit

let windowInfoDicts = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [NSDictionary]
let statusItemInfoDicts = windowInfoDicts.filter { $0[kCGWindowLayer] as! Int == 25 }

// You might want to remove "SystemUIServer" and "Control Center", which own the built-in
// system status items.
let processNamesWithStatusItems = Set(statusItemInfoDicts.map { $0[kCGWindowOwnerName] as! String })
print(processNamesWithStatusItems)

尽管我认为最好将这些字典解压到一些更简单的 Swift 结构中。它有点样板,但它使调用代码更加简单。

import AppKit
import CoreGraphics

struct WindowInfo {
    static var allOnScreenWindows: [WindowInfo] {
        let windowInfoDicts = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [NSDictionary]
        return windowInfoDicts.map(WindowInfo.init)
    }
    
    let name: String?
    let ownerName: String
    let ownerProcessID: pid_t
    let layer: Int
    let bounds: NSRect
    let alpha: Double
    let isOnscreen: Bool
    let memoryUsage: Measurement<UnitInformationStorage>
    let windowNumber: Int
    let sharingState: CGWindowSharingType
    let backingStoreType: CGWindowBackingType
    let otherAttributes: NSDictionary
    var isStatusMenuItem: Bool { layer == 25 }
    var isThirdPartyItem: Bool { ownerName != "SystemUIServer" && ownerName != "Control Center" }
}

extension WindowInfo {
    init(fromDict dict: NSDictionary) {
        let boundsDict = dict[kCGWindowBounds] as! NSDictionary

        var bounds = NSRect()
        assert(CGRectMakeWithDictionaryRepresentation(boundsDict, &bounds))
                
        let otherAttributes = NSMutableDictionary(dictionary: dict)
        otherAttributes.removeObjects(forKeys: [
            kCGWindowName,
            kCGWindowOwnerName,
            kCGWindowOwnerPID,
            kCGWindowLayer,
            kCGWindowBounds,
            kCGWindowAlpha,
            kCGWindowIsOnscreen,
            kCGWindowMemoryUsage,
            kCGWindowNumber,
            kCGWindowSharingState,
            kCGWindowStoreType,
        ])
        
        self.init(
            name: dict[kCGWindowName] as! String?,
            ownerName: dict[kCGWindowOwnerName] as! String,
            ownerProcessID: dict[kCGWindowOwnerPID] as! pid_t,
            layer: dict[kCGWindowLayer] as! Int,
            bounds: bounds,
            alpha: dict[kCGWindowAlpha] as! Double,
            isOnscreen: dict[kCGWindowIsOnscreen] as! Bool,
            memoryUsage: Measurement<UnitInformationStorage>(value: dict[kCGWindowMemoryUsage] as! Double, unit: .bytes),
            windowNumber: dict[kCGWindowNumber] as! Int,
            sharingState: CGWindowSharingType(rawValue: dict[kCGWindowSharingState] as! UInt32)!,
            backingStoreType: CGWindowBackingType(rawValue: dict[kCGWindowStoreType] as! UInt32)!,
            otherAttributes: otherAttributes
        )
    }
}

调用代码就好多了:

// Sort it from left to right.
// Note: these repeat once per screen, so you might want to de-duplicate them.
let statusItemWindows = WindowInfo.allOnScreenWindows.filter(\.isStatusMenuItem).sorted { $0.bounds.origin.x < $1.bounds.origin.x }
let processesWithStatusItems = Set(statusItemWindows.filter(\.isThirdPartyItem).map(\.ownerName))
print(processesWithStatusItems)

Here's an updated snippet in Swift:

import AppKit

let windowInfoDicts = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [NSDictionary]
let statusItemInfoDicts = windowInfoDicts.filter { $0[kCGWindowLayer] as! Int == 25 }

// You might want to remove "SystemUIServer" and "Control Center", which own the built-in
// system status items.
let processNamesWithStatusItems = Set(statusItemInfoDicts.map { $0[kCGWindowOwnerName] as! String })
print(processNamesWithStatusItems)

Though I think it's better to unpack these dictionaries into some simpler Swift structs. It's a bit more boilerplate, but it makes the calling code much simpler.

import AppKit
import CoreGraphics

struct WindowInfo {
    static var allOnScreenWindows: [WindowInfo] {
        let windowInfoDicts = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) as! [NSDictionary]
        return windowInfoDicts.map(WindowInfo.init)
    }
    
    let name: String?
    let ownerName: String
    let ownerProcessID: pid_t
    let layer: Int
    let bounds: NSRect
    let alpha: Double
    let isOnscreen: Bool
    let memoryUsage: Measurement<UnitInformationStorage>
    let windowNumber: Int
    let sharingState: CGWindowSharingType
    let backingStoreType: CGWindowBackingType
    let otherAttributes: NSDictionary
    var isStatusMenuItem: Bool { layer == 25 }
    var isThirdPartyItem: Bool { ownerName != "SystemUIServer" && ownerName != "Control Center" }
}

extension WindowInfo {
    init(fromDict dict: NSDictionary) {
        let boundsDict = dict[kCGWindowBounds] as! NSDictionary

        var bounds = NSRect()
        assert(CGRectMakeWithDictionaryRepresentation(boundsDict, &bounds))
                
        let otherAttributes = NSMutableDictionary(dictionary: dict)
        otherAttributes.removeObjects(forKeys: [
            kCGWindowName,
            kCGWindowOwnerName,
            kCGWindowOwnerPID,
            kCGWindowLayer,
            kCGWindowBounds,
            kCGWindowAlpha,
            kCGWindowIsOnscreen,
            kCGWindowMemoryUsage,
            kCGWindowNumber,
            kCGWindowSharingState,
            kCGWindowStoreType,
        ])
        
        self.init(
            name: dict[kCGWindowName] as! String?,
            ownerName: dict[kCGWindowOwnerName] as! String,
            ownerProcessID: dict[kCGWindowOwnerPID] as! pid_t,
            layer: dict[kCGWindowLayer] as! Int,
            bounds: bounds,
            alpha: dict[kCGWindowAlpha] as! Double,
            isOnscreen: dict[kCGWindowIsOnscreen] as! Bool,
            memoryUsage: Measurement<UnitInformationStorage>(value: dict[kCGWindowMemoryUsage] as! Double, unit: .bytes),
            windowNumber: dict[kCGWindowNumber] as! Int,
            sharingState: CGWindowSharingType(rawValue: dict[kCGWindowSharingState] as! UInt32)!,
            backingStoreType: CGWindowBackingType(rawValue: dict[kCGWindowStoreType] as! UInt32)!,
            otherAttributes: otherAttributes
        )
    }
}

The calling code is then much nicer:

// Sort it from left to right.
// Note: these repeat once per screen, so you might want to de-duplicate them.
let statusItemWindows = WindowInfo.allOnScreenWindows.filter(\.isStatusMenuItem).sorted { $0.bounds.origin.x < $1.bounds.origin.x }
let processesWithStatusItems = Set(statusItemWindows.filter(\.isThirdPartyItem).map(\.ownerName))
print(processesWithStatusItems)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文