我在查找如何使用pyobjc在Catalina上发送本地通知时遇到了一些困难。
我看到的关闭示例是这样的:PyObjC "Notifications are not allowed for this application"
我也一直在寻找这个答案,所以我想分享我发现的内容:
您首先注意到的是,函数notify()
定义了一个类,然后返回它的一个实例。您可能想知道为什么您不能直接致电Notification.send(params)
。我尝试过,但是PyObjC出现错误,很遗憾我无法修复:
# Error
class Notification(NSObject):
objc.BadPrototypeError: Objective-C expects 1 arguments, Python argument has 2 arguments for <unbound selector send of Notification at 0x10e410180>
现在输入代码:
# vscode may show the error: "No name '...' in module 'Foundation'; you can ignore it"
from Foundation import NSUserNotification, NSUserNotificationCenter, NSObject, NSDate
from PyObjCTools import AppHelper
def notify(
title='Notification',
subtitle=None, text=None,
delay=0,
action_button_title=None,
action_button_callback=None,
other_button_title=None,
other_button_callback=None,
reply_placeholder=None,
reply_callback=None
):
class Notification(NSObject):
def send(self):
notif = NSUserNotification.alloc().init()
if title is not None:
notif.setTitle_(title)
if subtitle is not None:
notif.setSubtitle_(subtitle)
if text is not None:
notif.setInformativeText_(text)
# notification buttons (main action button and other button)
if action_button_title:
notif.setActionButtonTitle_(action_button_title)
notif.set_showsButtons_(True)
if other_button_title:
notif.setOtherButtonTitle_(other_button_title)
notif.set_showsButtons_(True)
# reply button
if reply_callback:
notif.setHasReplyButton_(True)
if reply_placeholder:
notif.setResponsePlaceholder_(reply_placeholder)
NSUserNotificationCenter.defaultUserNotificationCenter().setDelegate_(self)
# setting delivery date as current date + delay (in seconds)
notif.setDeliveryDate_(NSDate.dateWithTimeInterval_sinceDate_(delay, NSDate.date()))
# schedule the notification send
NSUserNotificationCenter.defaultUserNotificationCenter().scheduleNotification_(notif)
# on if any of the callbacks are provided, start the event loop (this will keep the program from stopping)
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
def userNotificationCenter_didDeliverNotification_(self, center, notif):
print('delivered notification')
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
reply_callback(reply_text)
AppHelper.stopEventLoop()
# create the new notification
new_notif = Notification.alloc().init()
# return notification
return new_notif
def main():
n = notify(
title='Notification',
delay=0,
action_button_title='Action',
action_button_callback=lambda: print('Action'),
# other_button_title='Other',
# other_button_callback=lambda: print('Other'),
reply_placeholder='Enter your reply please',
reply_callback=lambda reply: print('Replied: ', reply),
)
n.send()
if __name__ == '__main__':
main()
notify()
函数采用了很多参数(它们是不言自明的)。 delay
是通知将在几秒钟后出现。请注意,如果您设置的延迟时间长于程序的执行时间,则该通知将在程序执行后立即发送。
您将看到按钮参数。按钮共有三种类型:
所有这些if
语句都在正确设置按钮,并且易于说明。例如,如果未提供其他按钮的参数,则不会显示“其他”按钮。
您会注意到的是,如果有按钮,我们将开始控制台事件循环:
if action_button_callback or other_button_callback or reply_callback:
print('started')
AppHelper.runConsoleEventLoop()
这是Python Objective-C的一部分。这不是一个很好的解释,但是它基本上使程序保持“打开”状态(我希望有人可以给出更好的解释)。
基本上,如果您指定要使用按钮,则程序将继续处于“打开”状态,直到AppHelper.stopEventLoop()
(稍后对此有进一步的介绍。)>
现在有一些“挂钩”功能:
userNotificationCenter_didDeliverNotification_(self, notification_center, notification)
:在发送通知时调用]]userNotificationCenter_didActivateNotification_(self, notification_center, notification)
:当用户与通知交互(单击,单击操作按钮或回复)(documentation)]时调用)肯定还有更多,但是不幸的是,我不认为该通知会被撤消或忽略。
使用userNotificationCenter_didActivateNotification_
,我们可以定义一些回调:
def userNotificationCenter_didActivateNotification_(self, center, notif):
print('did activate')
response = notif.response()
if notif.activationType() == 1:
# user clicked on the notification (not on a button)
# don't stop event loop because the other buttons can still be pressed
pass
elif notif.activationType() == 2:
# user clicked on the action button
# action button callback
action_button_callback()
AppHelper.stopEventLoop()
elif notif.activationType() == 3:
# user clicked on the reply button
reply_text = response.string()
# reply button callback
reply_callback(reply_text)
AppHelper.stopEventLoop()
动作类型有不同的激活类型。如图所示,还可以检索来自回复操作的文本。
您还将注意到结尾处的AppHelper.stopEventLoop()
。这意味着要“终止”程序的执行,因为该通知已由用户处理。
现在让我们解决此解决方案的所有问题。
AppHelper.stopEventLoop()
。AppHelper.stopEventLoop()
在交互后正在运行,因此无法发送带有回调的多个通知,因为程序将在与第一个通知交互后停止执行。如果您希望通过回调通知,由于我已解决的问题,您可能不应该这样做。 如果您只想显示关于警告用户的通知,是的。其他解决方案
PYNC是terminal-notifier的包装。但是,它们都在2018年接受了最后一次提交。Alerter似乎是terminal-notifier的后继者,但没有Python包装器。
您也可以尝试运行applescript发送通知,但是您不能设置回调,也不能更改图标。
我希望这个答案对您有所帮助。我还试图找出如何在Mac OS上可靠地通过回调发送通知。我已经弄清楚了如何发送通知,但是回调是问题所在。