我正在使用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
从另一个终端窗口,应用程序立即关闭。
答案 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。