swift 如何获取 * 其他 * 应用程序的坞站标记文本?

ukqbszuj  于 2023-01-19  发布在  Swift
关注(0)|答案(2)|浏览(153)

我正在为自己制作macOS Dock应用程序,但现在我卡住了。
如何获得所有其他应用程序的徽章计数?
或者,我想知道,Swift是如何监控MacOS系统通知事件的?这样我就可以更新一个红点来通知那里的用户正在收到新的通知。
我正在使用uBar Dock,我看到它可以为所有正在运行的应用程序计算徽章计数。我不知道如何做到这一点。

谢谢大家!

omhiaaxx

omhiaaxx1#

这是一个概念验证脚本,它显示了使用私有LaunchServices API是可能的。我在Python脚本(使用PyObjC,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>"放在上面。

xxe27gdn

xxe27gdn2#

***@ Alexandria ***已经用python和PyObjC库提供了答案,这里基本上只是用Swift和CFBundle重写了答案:

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 ***的答案也是如此。

相关问题