user4815162342
user4815162342

Reputation: 154876

Peeking a GTK event; gtk.gdk.event_peek always returns None

I need to check whether the Escape key has been pressed during execution of some non-GUI code. (The code is in Python, but can easily call into C if necessary.) The code received a function from the GUI that it occasionally calls to check whether it has been interrupted. The question is how to implement this check.

By looking at the documentation, gdk_event_peek seems like an excellent choice for this:

def _check_esc(self):
    event = gtk.gdk.event_peek()
    if event is None or event.type not in (gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE):
        return False
    return gtk.gdk.keyval_name(event.keyval) == 'Escape'

This doesn't work, however: the event returned from gtk.gdk.event_peek() is always None when the main loop is not running. Changing it to gtk.gdk.display_get_default().peek_event() doesn't help either. I assume the events are in the X event queue and are not yet moved to the GDK event queue. The documentation says:

Note that this function will not get more events from the windowing system. It only checks the events that have already been moved to the GDK event queue.

So, how does one transfer the event to the GDK event queue or? In other words, when does gtk.gdk.peek_event() ever return an event? Calling gtk.events_pending() doesn't have any effect.

Here is a minimal program to test it:

import gtk, gobject
import time

def code(check):
    while 1:
        time.sleep(.1)
        if check():
            print 'interrupted'
            return

def _check_esc():
    event = gtk.gdk.event_peek()
    print 'event:', event
    if event is None or event.type not in (gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE):
        return False
    return gtk.gdk.keyval_name(event.keyval) == 'Escape'

def runner():
    code(_check_esc)
    gtk.main_quit()

w = gtk.Window()
w.show()
gobject.idle_add(runner)
gtk.main()

When running the code, the event printed is always None, even if you press Escape or move the mouse.

I also considered installing a handler for Escape and having the checker process events with the while gtk.events_pending(): gtk.main_iteration() idiom. This results in unqueuing and dispatch of all pending events, including keyboard and mouse events. The effect is that the GUI is responsive enabled while the code runs, which doesn't look well and can severely interfere with the execution of the code. The only event processed during execution should be the escape key to interrupt it.

Upvotes: 0

Views: 1148

Answers (1)

user4815162342
user4815162342

Reputation: 154876

I came up with a runner implementation that satisfies the criteria put forward in the question:

def runner():
    # _check_esc searches for Escape in our queue
    def _check_esc():
        oldpos = len(queue)
        while gtk.events_pending():
            gtk.main_iteration()
        new = itertools.islice(queue, oldpos, None)
        return any(event.type == gtk.gdk.KEY_PRESS \
                       and gtk.gdk.keyval_name(event.keyval) == 'Escape'
                   for event in new)

    queue = []
    # temporarily set the global event handler to queue
    # the events
    gtk.gdk.event_handler_set(queue.append)
    try:
        code(_check_esc)
    finally:
        # restore the handler and replay the events
        handler = gtk.main_do_event
        gtk.gdk.event_handler_set(gtk.main_do_event)
        for event in queue:
            handler(event)
    gtk.main_quit()

Compared to a peek-based solution, its advantage is that it handles the case when another event arrives after the keypress. The disadvantage is that it requires fiddling with the global event handler.

Upvotes: 1

Related Questions