Reputation: 569
I am writing a Python application using GTK for the GUI. I noticed that closing it with Ctrl-C from the terminal isn't working and I discovered this is because of a bug, so I tried to manually handle the signal. The problem is that if I set the default behaviour to the default one, the signal is caught and the application is closed correctly, but if I use a custom handler it doesn't work. Here is my (simplified) code:
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()
If, instead, I set the default behaviour, the signal is caught correctly:
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()
Am I missing something?
EDIT:
I tried some more and I noticed that the signal is actually captured, but the window is not shutdown immediately, but only when the focus has been acquired again. If, instead, I run a
kill -9 pid
from another terminal window, the application is closed immediately.
Upvotes: 6
Views: 4319
Reputation: 111
I played around with a few different approaches, including having a separate thread for running glib mainloop and catching signal from another but in the end it was as simple as using 'try':
from gi.repository import GLib
main_loop = GLib.MainLoop()
try:
main_loop.run()
except KeyboardInterrupt:
print("How rude!")
Upvotes: 5
Reputation: 146
I also remember having lots of trouble regarding signal handling while learning appindicators with pygtk3. Here a working example demonstrating how it can be done for SIGHUP, SIGINT and 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()
Note that when recieving a signal, if you don't exit gtk loop (Gtk.main_quit()) then when it recieves a signal for the second time it will close itself, probably because of the bug you mentioned. Nontheless for cleaning up variables right before exiting (including with CTRL + C) still works perfect.
If I recall correctly I got the solution from a person in pygtk irc channel, so I cannot give the right credit to the person that provided me with the solution.
Upvotes: 6
Reputation: 55499
I can't exactly reproduce your problem because I'm running GTK2 (gtk version: 2.21.3, pygtk version: 2.17.0, on Linux, to be precise). My GTK programs die with a KeyboardInterrupt exception when ^C is pressed, and I can trap that using a try: ... except KeyboardInterrupt:
block.
But I do get the same results as you when I set a signal handler in the __init__
method of a GTK GUI: i.e., using the default signal.SIG_DFL
handler works as expected, but a custom handler does not.
However, it does work for me if I set the signal handler outside the GUI class. Here's a demo program with an Entry box in the GUI so that the GUI has some state info to report at cleanup time.
#! /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()
Hopefully, this technique also works in GTK3.
Upvotes: 1