我正在为自己制作macOS Dock应用程序,但现在我卡住了。如何获得所有其他应用程序的徽章计数?或者,我想知道,Swift是如何监控MacOS系统通知事件的?这样我就可以更新一个红点来通知那里的用户正在收到新的通知。我正在使用uBar Dock,我看到它可以为所有正在运行的应用程序计算徽章计数。我不知道如何做到这一点。
谢谢大家!
omhiaaxx1#
这是一个概念验证脚本,它显示了使用私有LaunchServices API是可能的。我在Python脚本(使用PyObjC,pip3 install PyObjC)中这样做,因为它更容易在单个代码块中捕获,而且与混乱的头文件、dlopen调用等相比,它更容易破解。这可能与lsappinfo在内部所做的相同。
pip3 install PyObjC
dlopen
lsappinfo
#!/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) kLSDefaultSessionID = 0xfffffffe # The actual value is `int -2` badge_label_key = "StatusLabel" # TODO: Is there a `_kLS*` constant for this? app_asns = _LSCopyRunningApplicationArray(kLSDefaultSessionID) app_infos = [_LSCopyApplicationInformation(kLSDefaultSessionID, asn, None) for asn in app_asns] 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)
受@Ranoiaetep回答的启发,我将在Swift中这样写:
import Foundation import CoreFoundation let CoreServiceBundle = CFBundleGetBundleWithIdentifier("com.apple.CoreServices" as CFString) typealias LSASN = CFTypeRef let kLSDefaultSessionID: Int32 = -2 let badgeLabelKey = "StatusLabel" // TODO: Is there a `_kLS*` constant for this? typealias _LSCopyRunningApplicationArray_Type = @convention(c) (Int32) -> [LSASN] let _LSCopyRunningApplicationArray: _LSCopyRunningApplicationArray_Type = { let untypedFnPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyRunningApplicationArray" as CFString) return unsafeBitCast(untypedFnPtr, to: _LSCopyRunningApplicationArray_Type.self) }() typealias _LSCopyApplicationInformation_Type = @convention(c) (Int32, CFTypeRef, CFString?) -> [CFString: CFDictionary] let _LSCopyApplicationInformation: _LSCopyApplicationInformation_Type = { let untypedFnPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyApplicationInformation" as CFString) return unsafeBitCast(untypedFnPtr, to: _LSCopyApplicationInformation_Type.self) }() func getAllAppASNs() -> [LSASN] { _LSCopyRunningApplicationArray(kLSDefaultSessionID) } func getAppInfo(asn: LSASN, property: String? = nil) -> [String: Any] { _LSCopyApplicationInformation(kLSDefaultSessionID, asn, property as CFString?) as [String: Any] } let apps = getAllAppASNs() let appInfos = apps.map { getAppInfo(asn: $0) } let appBadges = Dictionary(uniqueKeysWithValues: appInfos.compactMap { appInfo -> (key: String, value: String)? in guard let badgeLabel = appInfo[badgeLabelKey] else { return nil } // It's posisble to make apps with no bundle let appName = appInfo[kCFBundleNameKey as String] as! String? ?? "<no bundle name>" let badgeString = (badgeLabel as! [String: String])["label"]! return (key: appName, value: badgeString) } ) appBadges.forEach { k, v in print("\(k): '\(v)'")}
您可以轻松地测试它,只需制作一个带有徽章标签的最小应用,如下所示:
import AppKit NSApplication.shared.setActivationPolicy(.regular) NSApplication.shared.dockTile.badgeLabel = "123" NSApplication.shared.run()
有趣的是,这样的应用程序没有包(因此没有包名),这就是为什么我把?? "<no bundle name>"放在上面。
?? "<no bundle name>"
xxe27gdn2#
***@ Alexandria ***已经用python和PyObjC库提供了答案,这里基本上只是用Swift和CFBundle重写了答案:
PyObjC
let CoreServiceBundle = CFBundleGetBundleWithIdentifier("com.apple.CoreServices" as CFString) let GetRunningApplicationArray: () -> [CFTypeRef] = { let functionPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyRunningApplicationArray" as CFString) return unsafeBitCast(functionPtr,to:(@convention(c)(UInt)->[CFTypeRef]).self)(0xfffffffe) } let GetApplicationInformation: (CFTypeRef) -> [String:CFTypeRef] = { app in let functionPtr = CFBundleGetFunctionPointerForName(CoreServiceBundle, "_LSCopyApplicationInformation" as CFString) return unsafeBitCast(functionPtr, to: (@convention(c)(UInt, Any, Any)->[String:CFTypeRef]).self)(0xffffffff, app, 0) } let badgeLabelKey = "StatusLabel" let apps = GetRunningApplicationArray() let appInfos = apps.map { GetApplicationInformation($0) } let appBadges = appInfos .filter{ $0.keys.contains(badgeLabelKey) } .reduce(into: [:]) { $0[$1[kCFBundleNameKey as String] as! String] = ($1[badgeLabelKey] as! [String:CFTypeRef])["label"] } print(appBadges)
有一点需要注意的是,通过这种方法,似乎无法检测到来自系统应用程序的徽章,这些应用程序可以在不活动时更新徽章,如消息、FaceTime、系统设置。我认为***@ Alexandria ***的答案也是如此。
2条答案
按热度按时间omhiaaxx1#
这是一个概念验证脚本,它显示了使用私有LaunchServices API是可能的。我在Python脚本(使用PyObjC,
pip3 install PyObjC
)中这样做,因为它更容易在单个代码块中捕获,而且与混乱的头文件、dlopen
调用等相比,它更容易破解。这可能与
lsappinfo
在内部所做的相同。受@Ranoiaetep回答的启发,我将在Swift中这样写:
您可以轻松地测试它,只需制作一个带有徽章标签的最小应用,如下所示:
有趣的是,这样的应用程序没有包(因此没有包名),这就是为什么我把
?? "<no bundle name>"
放在上面。xxe27gdn2#
***@ Alexandria ***已经用python和
PyObjC
库提供了答案,这里基本上只是用Swift和CFBundle重写了答案:有一点需要注意的是,通过这种方法,似乎无法检测到来自系统应用程序的徽章,这些应用程序可以在不活动时更新徽章,如消息、FaceTime、系统设置。我认为***@ Alexandria ***的答案也是如此。