Pyrox
Pyrox

Reputation: 569

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.

Upvotes: 6

Views: 4319

Answers (3)

zeenix
zeenix

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

Ascot
Ascot

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

PM 2Ring
PM 2Ring

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

Related Questions