【问题标题】:How create local notification on MacOS Catalina pyobjc?如何在 MacOS Catalina pyobjc 上创建本地通知?
【发布时间】:2020-09-25 18:19:36
【问题描述】:

我很难找到如何使用 pyobjc 在 Catalina 上发送本地通知。

我看到的关闭示例是这样的: PyObjC "Notifications are not allowed for this application"

【问题讨论】:

  • 任何人来这里,看看上面发布的示例 - 如果您使用 Python 的签名版本(例如从 python.org 下载的版本),它实际上可以工作

标签: python objective-c macos macos-catalina pyobjc


【解决方案1】:

编辑(2020 年 6 月 27 日):我创建了一个包,它具有在 Mac OS here 上显示通知的功能。它将使用 PyObjC 创建和显示通知。如果由于某种原因它不起作用,它将回退到带有osascript 的 AppleScript 通知。我做了一些测试,发现 PyObjC 通知在某些设备上有效,但在某些设备上无效。

答案:

我也一直在寻找这个答案,所以我想分享一下我的发现:

您会注意到的第一件事是函数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 是通知将在几秒后出现。请注意,如果您设置的延迟时间比程序的执行时间长,则通知将在程序执行后发送。

您将看到按钮参数。共有三种按钮:

  1. 动作按钮:主导动作
  2. 其他按钮:辅助操作
  3. 回复按钮:打开文本字段并接受用户输入的按钮。这在 iMessage 等消息应用中很常见。

所有这些if 语句都在适当地设置按钮并且不言自明。例如,如果没有提供其他按钮的参数,则不会显示其他按钮。

您会注意到,如果有按钮,我们将启动控制台事件循环:

      if action_button_callback or other_button_callback or reply_callback:
        print('started')
        AppHelper.runConsoleEventLoop()

这是 Python Objective-C 的一部分。这不是一个很好的解释,但它基本上让程序“运行”(我希望有人能给出更好的解释)。

基本上,如果你指定你想要一个按钮,程序将继续“开启”直到AppHelper.stopEventLoop()(稍后会详细介绍)。

现在有一些“挂钩”功能:

  1. userNotificationCenter_didDeliverNotification_(self, notification_center, notification):发送通知时调用
  2. 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()。这意味着“结束”程序的执行,因为通知已被用户处理。

现在让我们解决这个解决方案的所有问题。

问题

  1. 如果用户不与通知交互,程序将永远不会停止。通知将滑入通知中心,可能会或可能永远不会与之交互。正如我之前所说,没有通知被忽略或通知被驳回的钩子,所以我们不能在这种时候调用AppHelper.stopEventLoop()
  2. 因为AppHelper.stopEventLoop()是在交互后运行的,所以无法通过回调发送多个通知,因为在第一个通知交互后程序将停止执行。
  3. 虽然我可以显示其他按钮(并给它文本),但我找不到给它一个回调的方法。这就是为什么我没有在上面的代码块中解决它。我可以给它文本,但它本质上是一个虚拟按钮,因为它不能做任何事情。

我还应该使用这个解决方案吗?

如果您想要带有回调的通知,您可能不应该这样做,因为我已经解决了这些问题。

如果您只想显示通知以提醒用户某事,可以。

其他解决方案

PYNCterminal-notifier 的包装。但是,两者都在 2018 年收到了最后一次提交。Alerter 似乎是终端通知程序的继任者,但没有 Python 包装器。

你也可以尝试运行applescript发送通知,但是不能设置回调,也不能改变图标。

我希望这个答案对你有所帮助。我还试图找出如何在 Mac OS 上可靠地发送带有回调的通知。我已经弄清楚如何发送通知,但问题是回调。

【讨论】:

  • 我也在寻找使用rubicon-objc的解决方案,如果你来了,我想它可能会解决你的一些问题,请在这里分享。
  • 你也能分享一下使用这段代码的项目吗??
  • 对不起,我没有使用此代码的项目,因为我只是出于测试目的而尝试它。感谢您告诉我有关 Rubicon objc 的信息。如果您设法找到使用它发送通知的最小工作示例,请通知我。我也会尽快尝试使用它
  • 我尝试运行代码,它只会输出'started'并且不会弹出任何通知,我使用的是python 3.7.7并且所有pyobjc都是6.2版
  • @Chromazmoves 检查您的 mac os 通知设置。通知应设置为横幅或警报。有时通知也需要几秒钟才能传递
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-25
  • 1970-01-01
  • 2020-02-13
  • 2020-02-06
相关资源
最近更新 更多