回调函数与GTK主循环

4 投票
1 回答
2168 浏览
提问于 2025-04-17 09:32

我正在尝试制作一个简单的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_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运行时错误。不过这表明,改变gtk.main()调用的位置确实有影响。

我尝试查看Gtk和Pygtk的文档,了解主循环的使用,但最终没有找到解决办法。

所以我的问题是:正确的做法是什么,背后的逻辑是什么?

总结:如果我不把gtk.main()放在显示通知的同一个函数里,当应该触发时,action_callback函数就不会被调用。由于这个函数需要放在gtk主循环中,我就陷入了gtk主循环自我调用或action_callback函数不被触发的困境。

谢谢大家的帮助;)

1 个回答

3

这里的问题是,pynotify在处理未被引用的对象时有个bug。在你第一个代码片段中,当main()函数结束时,n就不再被引用了(这是基于cPython的引用计数机制)。

不幸的是,这意味着通知对象会被销毁,而相应的操作不会被调用(尽管你的通知守护进程仍然会显示通知)。

解决这个问题的方法是保持对这个通知的引用。最简单的做法是把你第一个代码片段中的n = pynotify.Notification改成self.last_notification = n = pynotify.Notification

如果你有多个通知,你需要把它们放在一个列表或集合里,但你还需要确保在触发操作和超时过期时都能正确删除它们。

撰写回答