观察新的系统通知 OSX



是否可以侦听/观察macOS收到的新通知?

我的意思是,当收到新的iMessage或Slack消息时(所以基本上导致通知中心显示通知的所有内容(

简短的回答:这是不可能的。

除非应用程序提供特定 API,否则无法观察应用程序发送的用户通知。例如,iMessage 和 Mail 的 AppleScript 字典包含脚本可以响应的事件。但是,用户通知封装在目标应用程序中。


有一个名为 DistributedNotificationCenter 的全局通知类,这是一种通知调度机制,支持跨任务边界广播通知。一些进程正在发送分布式通知,但它是一个完全不同的功能,因为UserNotification。例如,TimeMachine 引擎进程backupd运行备份时发送分布式通知。

您可以使用以下方式订阅所有已分发的通知

DistributedNotificationCenter.default().addObserver(self, selector: #selector(handleNotifications(_:)), name: nil, object: nil)

但我怀疑iMessage在收到消息时会发送分布式通知。

在网上广泛搜索后,我能够使用AXUIElement找到解决方法。这是 Hammerspoon 的一个工作示例(它有一个很好的模块可用(,但也可以在 Swift 或 Objective-C 中使用同样的东西,尽管有点麻烦,因为它是一个低级 C API。也可以使用AppleScript或JXA。

local log = hs.logger.new("notificationcenter")
local notificationCenterBundleID = "com.apple.notificationcenterui"
local notificationCenter = hs.axuielement.applicationElement(notificationCenterBundleID)
assert(notificationCenter, "Unable to find Notification Center AX element")
local processedNotificationIDs = {}
local notificationSubroles = {
AXNotificationCenterAlert = true,
AXNotificationCenterBanner = true,
}
notificationObserver = hs.axuielement.observer
.new(notificationCenter:pid())
:callback(function(_, element)
-- Ignore events when system drawer is open to avoid callbacks for
-- previous notifications.
if notificationCenter:asHSApplication():focusedWindow() then return end
-- Process each notification only once.
if
not notificationSubroles[element.AXSubrole]
or processedNotificationIDs[element.AXIdentifier]
then
return
end
-- Only match Messages and Slack notifications.
local stackingID = element.AXStackingIdentifier
if
stackingID ~= "com.tinyspeck.slackmacgap"
and stackingID:find("com.apple.MobileSMSiMessage;", 1, true) ~= 1
and stackingID:find("com.apple.MobileSMSSMS;", 1, true) ~= 1
then
log.df("Skipping notification with stacking ID %s", stackingID)
return
end
processedNotificationIDs[element.AXIdentifier] = true
local staticTexts = hs.fnutils.imap(
hs.fnutils.ifilter(element, function(value)
return value.AXRole == "AXStaticText"
end),
function(value)
return value.AXValue
end
)
local title = nil
local subtitle = nil
local message = nil
if #staticTexts == 2 then
title, message = table.unpack(staticTexts)
elseif #staticTexts == 3 then
title, subtitle, message = table.unpack(staticTexts)
else
error(string.format("Unexpected static text count: %d", #staticTexts))
end
log.f(
"Got notification: title = %s, subtitle = %s, message = %s",
title,
subtitle,
message
)
end)
:addWatcher(
notificationCenter,
-- Equivalent to kAXLayoutChangedNotification ("AXLayoutChanged")
-- https://github.com/Hammerspoon/hammerspoon/blob/8ea7d105ab27c917703a6c30e5980b82a23c6a0c/extensions/axuielement/observer.m#L402
hs.axuielement.observer.notifications["layoutChanged"]
)
:start()

观察到的通知是AXLayoutChanged的,这不太理想,因为每当将鼠标悬停在通知上或抽屉移动时都会调用它,并且可能包括其他元素的事件。我尝试了其他几个,最接近的适合是AXCreated但似乎没有可靠地调用新通知。作为解决方法,上述代码仅查看在通知中心抽屉关闭时收到的通知,并使用AXIdentifier属性仅处理每个通知一次。

选择

这是我能找到的唯一工作方法。我尝试使用@vadian答案中提到的NSDistributedNotificationCenter,以及从strings系统二进制文件(如/System/Applications/Messages.app/System/Library/CoreServices/NotificationCenter.app/System/Library/CoreServices/UserNotificationCenter.app(运行时生成的名称,但找不到任何用于消息或通知中心的通知。这可能是由于找不到正确的值,因为NSDistributedNotificationCenter不再支持nil名称。这是我用来检查的 Swift 脚本:

import Foundation
func readNames() throws -> [String] {
let fileURL = URL(fileURLWithPath: "/path/to/notification-names.txt")
return try String(contentsOf: fileURL, encoding: .utf8).components(separatedBy: "n")
}
let nc = DistributedNotificationCenter.default()
let names = try readNames()
print(names)
for name in names {
nc.addObserver(forName: NSNotification.Name(name), object: nil, queue: nil) { notification in
print(notification)
}
}
print("Running")
RunLoop.main.run()

notification-names.txt可以通过以下方式生成:strings /System/Library/CoreServices/NotificationCenter.app/Contents/MacOS/NotificationCenter | grep -F com.apple.

还有一个数据库文件可以查询和监视更改(请参阅此处和此处的先前线程(,但是在我所做的有限测试中,它不是实时写入的,这与上述带有AXUIElement的方法不同。

最新更新