I am making a macOS Dock application for myself, but now I'm stuck.
How to get the badge count of all other applications?
Or, I want to know, how does Swift monitor the macOS system notification events? So I can update a red spot to notice that users there are getting new notifications.
I am using the uBar Dock, I see it can take the badge count for all running applications. I can't figure it out how to do this.
Thank you!
CodePudding user response:
Here's a proof-of-concept script that shows that it is possible, using private LaunchServices APIs. I did it in a Python script (which uses PyObjC, pip3 install PyObjC
) because it's a bit easier to capture in a single code block, and it's easier to hack around compared to a mess of headers, dlopen
calls, etc.
This is probably the same as what lsappinfo
does internally.
#!/usr/bin/env python3
#ref: https://gist.github.com/pudquick/eebc4d569100c8e3039bf3eae56bee4c
from Foundation import NSBundle
import objc
CoreServices = NSBundle.bundleWithIdentifier_('com.apple.CoreServices')
functions = [
('_LSCopyRunningApplicationArray', b'@I'),
('_LSCopyApplicationInformation', b'@I@@'),
]
constants = [
('_kLSDisplayNameKey', b'@'),
]
objc.loadBundleFunctions(CoreServices, globals(), functions)
objc.loadBundleVariables(CoreServices, globals(), constants)
badge_label_key = "StatusLabel" # Is there a `_kLS*` constant for this?
apps = _LSCopyRunningApplicationArray(0xfffffffe)
app_infos = [_LSCopyApplicationInformation(0xffffffff, x, None) for x in apps]
app_badges = { app_info.get(_kLSDisplayNameKey): app_info[badge_label_key].get("label", None)
for app_info in app_infos if badge_label_key in app_info }
print(app_badges)
If you want to call this from your Swift (or for dynamic loading and method calling, Objective-C might actually be easier here), you'll need to port this over. (e.g. using dlopen
to get reference to these functions)
CodePudding user response:
@Alexander already provided an answer in python with PyObjC
library, here is basically just rewriting that answer with Swift using CFBundle:
let CoreServiceBundle = CFBundleGetBundleWithIdentifier("com.apple.CoreServices" as CFString)
let _LSCopyRunningApplicationArray: () -> [CFBundle] = {
let functionPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyRunningApplicationArray" as CFString)
return unsafeBitCast(functionPtr,to:(@convention(c)(UInt)->[CFBundle]).self)(0xfffffffe)
}
let _LSCopyApplicationInformation: (CFBundle) -> [String:CFBundle] = { app in
let functionPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyApplicationInformation" as CFString)
return unsafeBitCast(functionPtr, to: (@convention(c)(UInt, Any, Any)->[String:CFBundle]).self)(0xffffffff, app, 0)
}
var badgeLabelKey = "StatusLabel"
let apps = _LSCopyRunningApplicationArray()
let appInfos = apps.map { _LSCopyApplicationInformation($0) }
let appBadges = appInfos
.filter{ $0.keys.contains(badgeLabelKey) }
.reduce(into: [:]) { $0[$1[kCFBundleNameKey as String] as! String] = ($1[badgeLabelKey] as! [String:CFBundle])["label"] }
print(appBadges)
One thing to note is that it seems like badges from system apps that are able to update badge while not being active, such as Messages, FaceTime, System Settings, won't be detected through this method. And I would assume the same with @Alexander's answer.