Reputation: 86
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
Upvotes: 5
Views: 1397
Reputation: 63359
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)
Upvotes: 1
Reputation: 53561
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).
Upvotes: 8