回调函数和GTK主循环

4
我正在尝试制作一个简单的Unix桌面应用程序,它使用pynotify通知系统向用户显示一些警报,并允许他们从放置在警报上的按钮启动相关应用程序。
以下是相关的简化代码:
import subprocess, pynotify, gobject, gtk

class Notifier():
    def __init__(self):
        pynotify.init('Notifications')
        n = pynotify.Notification("Some stuff")
        n.add_action("action", "Action", self.action_callback)
        n.show()
        gtk.main()

    def action_callback(self, n, action):
        subprocess.Popen(['ls', '/'])

if __name__ == '__main__':
    Notifier()

这段话的意思是:“这个功能很好(它会显示一个通知弹窗,其中有一个“操作”按钮,当激活时可以触发ls /命令),直到我实际尝试将通知部分放入循环中(我需要定期轮询服务器以获取通知,然后显示)。我已经尝试过这样做:”
import subprocess, pynotify, gobject, gtk

class Notifier():
    def __init__(self):
        pynotify.init('Notifications')
        gobject.timeout_add(0, self.main)
        gtk.main()

    def action_callback(self, n, action):
        subprocess.Popen(['ls', '/'])

    def main(self):
        n = pynotify.Notification("Some stuff")
        n.add_action("action", "Action", self.action_callback)
        n.show()
        gobject.timeout_add(10000, self.main)

if __name__ == '__main__':
    Notifier()

由于某些原因,单击“Action”按钮时,“action_callback”函数不再被调用。
似乎这是我使用Gtk主循环的方式存在问题。像这样做可以使函数实际被触发:
import subprocess, pynotify, gobject, gtk

class Notifier():
    def __init__(self):
        pynotify.init('Notifications')
        self.main()

    def action_callback(self, n, action):
        subprocess.Popen(['ls', '/'])

    def main(self):
        n = pynotify.Notification("Some stuff")
        n.add_action("action", "Action", self.action_callback)
        n.show()
        gobject.timeout_add(10000, self.main)
        gtk.main()

if __name__ == '__main__':
    Notifier()

当然,这不是一个正确的解决方案,我很快就会收到一个“超出最大递归深度”的Python RuntimeError。但是它表明改变gtk.main()调用的位置是有影响的。
我尝试查看关于主循环的Gtk和Pygtk文档,但最终没有找到解决方案。
所以我的问题是:什么是正确的做法,背后的逻辑是什么?
简而言之,如果我不将gtk.main()放在显示通知的同一函数中,当应该触发action_callback函数时,它就不会被触发。由于这个函数需要放在gtk主循环中,我就陷入了让gtk主循环调用自身或者action_callback函数不被触发的困境中。
提前感谢您的任何帮助;)

gtk.main是一个阻塞调用,递归地在函数中调用它不是一个好主意!为什么不添加轮询服务器的代码,而不是将self.main作为超时函数添加呢?不幸的是,我对pynotify或python一无所知 ): ... - another.anon.coward
将轮询代码放在自己的函数中并不能解决问题,因为通知部分需要在每次服务器轮询后立即调用,并且无法与gtk主循环调用分离。我想我需要的是一种方法,在不设置回调函数的同一函数中启动gtk.main()时仍然可以使其正常工作。我甚至不明白为什么我调用gtk.main()的位置会在这里产生影响。 - gentledevil
1个回答

3
这里的问题在于pynotify在未引用对象上的回调存在漏洞。在您的第一个片段中,假设在cPython中进行引用计数,当main()函数退出时,n将不被引用。
不幸的是,这意味着通知对象被销毁,而操作没有被调用(尽管您的通知守护程序仍将显示通知)。
解决方法是保留对该通知的引用。最简单的方法是将您的第一个片段更改为self.last_notification = n = pynotify.Notification
如果您有多个通知,则需要将它们放入列表或集合中,但然后您需要确保它们在触发操作和超时到期时被删除。

这同样适用于PyGObject和直接使用Notify API。你拯救了我和其他几个人的大脑! - Fenikso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接