5

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.

Pyrox
  • 551
  • 5
  • 17
  • Do you need to manually handle the signal, though? See [keyboard interrupt with with python gtk?](http://stackoverflow.com/questions/16410852/keyboard-interrupt-with-with-python-gtk) – PM 2Ring Oct 17 '14 at 12:37
  • Yes, I would like to set a handler to manage data being correctly written to file even if the program is killed by sending a SIGINT. – Pyrox Oct 17 '14 at 12:42

3 Answers3

6

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.

Ascot
  • 136
  • 1
  • 5
  • 1
    I tried it quickly by adapting it to my code and it seems to work. Pity that you need to do all this stuff just for "basic" signal handling. Hope they fix it in future. – Pyrox Oct 20 '14 at 08:36
  • > when it receives a signal for the second time it will close itself - I have hit this problem recently and I'm still wondering why it closes itself after receiving the second signal (any signal apparently, even USR1) – Hernn0 Jun 28 '21 at 19:58
5

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!")
zeenix
  • 111
  • 1
  • 3
1

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.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Actually after trying some more I noticed that the problem is the signal being captured correctly, but the window not being shut immediately, but only after the focus has ben acquired again. This happens only if I ctrl - C in the window where I launched the program, but if I run a `kill -9 pid` the application shuts down immediately. This behaviour (unfortunately) happens also with your technique. – Pyrox Oct 18 '14 at 10:02
  • Drats! Oh well, it was worth a try, I guess. Can you grab focus using `window.present()` or something? – PM 2Ring Oct 18 '14 at 12:32
  • Tried window.present() before quitting the GUI, but nothing changes. – Pyrox Oct 18 '14 at 14:21