Search code examples
pythongtksignalspygtkgtk3

Python GTK signal handler not working


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.


Solution

  • 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.