Python GTK信号处理程序无法正常工作

时间:2014-10-15 17:04:24

标签: python gtk signals pygtk gtk3

我正在使用GTK为GUI编写Python应用程序。我注意到从终端用Ctrl-C关闭它是行不通的,我发现这是因为一个bug,所以我试图手动处理信号。问题是如果我将默认行为设置为默认行为,则捕获信号并正确关闭应用程序,但如果我使用自定义处理程序则不起作用。这是我的(简化)代码:

from gi.repository import Gtk
import signal

class MainWindow(Gtk.Window):

    def __init__(self):
        ...
        signal.signal(signal.SIGINT, self.__signal_handler)

    def __signal_handler(self, signal, frame):
        print "Caught!"

    ...

if __name__ == "__main__":
    win = MainWindow()
    win.show_all()
    Gtk.main()

相反,如果我设置了默认行为,则会正确捕获信号:

from gi.repository import Gtk
import signal

    class MainWindow(Gtk.Window):

        def __init__(self):
            ...
            signal.signal(signal.SIGINT, signal.SIG_DFL)

        ...

    if __name__ == "__main__":
        win = MainWindow()
        win.show_all()
        Gtk.main()

我错过了什么吗?

编辑:

我尝试了一些,我注意到信号实际上已被捕获,但窗口并未立即关闭,而是仅在重新获得焦点时才关闭。相反,如果我运行

kill -9 pid

从另一个终端窗口,应用程序立即关闭。

3 个答案:

答案 0 :(得分:4)

我还记得在使用pygtk3学习appindicators时遇到很多关于信号处理的麻烦。这是一个演示如何为SIGHUP,SIGINT和SIGTERM完成的工作示例:

#!/usr/bin/python
from gi.repository import Gtk, GLib, GObject
from gi.repository import AppIndicator3 as appindicator
import os
import signal

class Gui():
    def __init__(self):
        self.window = Gtk.Window(title="Signal example")
        self.window.set_size_request(250,150)
        self.window.connect("delete-event", Gtk.main_quit)
        self.window.show_all()

    def cleanup(self):
        print("... Cleaning up variables, etc.")

    def quit(self, widget):
        print("... Exiting main gtk loop")
        Gtk.main_quit()

def InitSignal(gui):
    def signal_action(signal):
        if signal is 1:
            print("Caught signal SIGHUP(1)")
        elif signal is 2:
            print("Caught signal SIGINT(2)")
        elif signal is 15:
            print("Caught signal SIGTERM(15)")
        gui.cleanup()
        gui.quit(None)

    def idle_handler(*args):
        print("Python signal handler activated.")
        GLib.idle_add(signal_action, priority=GLib.PRIORITY_HIGH)

    def handler(*args):
        print("GLib signal handler activated.")
        signal_action(args[0])

    def install_glib_handler(sig):
        unix_signal_add = None

        if hasattr(GLib, "unix_signal_add"):
            unix_signal_add = GLib.unix_signal_add
        elif hasattr(GLib, "unix_signal_add_full"):
            unix_signal_add = GLib.unix_signal_add_full

        if unix_signal_add:
            print("Register GLib signal handler: %r" % sig)
            unix_signal_add(GLib.PRIORITY_HIGH, sig, handler, sig)
        else:
            print("Can't install GLib signal handler, too old gi.")

    SIGS = [getattr(signal, s, None) for s in "SIGINT SIGTERM SIGHUP".split()]
    for sig in filter(None, SIGS):
        print("Register Python signal handler: %r" % sig)
        signal.signal(sig, idle_handler)
        GLib.idle_add(install_glib_handler, sig, priority=GLib.PRIORITY_HIGH)

if __name__ == "__main__":
    gui = Gui()
    InitSignal(gui)
    Gtk.main()

请注意,当收到信号时,如果你没有退出gtk循环(Gtk.main_quit()),那么当它第二次收到信号时它会自动关闭,可能是因为你提到的错误。尽管如此,在退出之前清理变量(包括使用CTRL + C)仍然可以完美地完成。

如果我没记错的话,我从pygtk irc频道的人那里得到了解决方案,所以我无法给予向我提供解决方案的人员。

答案 1 :(得分:4)

我玩了几种不同的方法,包括使用一个单独的线程来运行glib mainloop和捕获来自另一个的信号,但最后它就像使用'try'一样简单:

from gi.repository import GLib

main_loop = GLib.MainLoop()

try:
    main_loop.run()
except KeyboardInterrupt:
    print("How rude!")

答案 2 :(得分:1)

我无法完全重现您的问题,因为我正在运行GTK2(确切地说,在Linux上运行GTK版本:2.21.3,pygtk版本:2.17.0)。当按下^ C时,我的GTK程序死于KeyboardInterrupt异常,我可以使用try: ... except KeyboardInterrupt:块来捕获它。

但是当我在GTK GUI的__init__方法中设置信号处理程序时,我得到与您相同的结果:即,使用默认的signal.SIG_DFL处理程序工作正如所料,但自定义处理程序没有。

但是,如果我将信号处理程序设置在 GUI类之外, 对我有用。这是一个演示程序,在GUI中有一个Entry框,这样GUI就可以在清理时报告一些状态信息。

#! /usr/bin/env python

''' Testing signal trapping in GTK '''

import signal

import pygtk
pygtk.require('2.0')
import gtk

class Demo:
    def cleanup(self):
        print "entry text: '%s'" % self.entry.get_text()
        print 'Quitting...'

    def quit(self, widget=None):
        self.cleanup()
        gtk.main_quit()

    def entry_activated_cb(self, widget):
        print "entry activated: '%s'" % widget.get_text()
        return True

    def __init__(self):
        win = gtk.Window(gtk.WINDOW_TOPLEVEL)
        win.connect("destroy", self.quit)
        win.set_border_width(5)

        self.entry = entry = gtk.Entry()
        entry.connect("activate", self.entry_activated_cb)
        win.add(entry)
        entry.show()

        win.show()


def main():
    def my_sigint_trap(signum, frame):
        print '\nSignal handler called with signal %d, %s' % (signum, frame)
        ui.quit()

    ui = Demo()
    signal.signal(signal.SIGINT, my_sigint_trap)
    gtk.main()


if __name__ == "__main__":
    main()

希望,这项技术也适用于GTK3。