Python, Queue, Pygame and the GLib mainloop

The GLib mainloop is what you're probably running in your Python program when you are using PyGTK or GStreamer.

Sometimes you want to integrate other event sources to it. The documentation for this is a bit thin; all I've seen is GLib's page on the main event loop, GSourceFuncs section and test-source.py in pygobject. Here I'm presenting two snippets displaying how to do it with two systems I've needed to work with. No guarantees on their correctness, but they do seem to work. Both are parts of larger modules, so they aren't usable by themselves and aren't really tidied up to look like proper examples. However, they should give you the general idea.

Here's how to do it with Python's Queue:

class QueueSource(gobject.Source):
    def __init__(self, queue):
        gobject.Source.__init__(self)
        self._queue = queue
        self.logger = loghelper.get_logger(self)

    def prepare(self):
        return False

    def check(self):
        return not self._queue.empty()

    def dispatch(self, callback, args):
        self.logger.debug("dispatch called, callback: " + str(callback))
        if callback is not None:
            self.logger.debug("dispatch calling callback: " + str(callback))
            v = callback(self._queue.get(), *args)
            self.logger.debug("dispatch returned " + str(v))
        return True

    def finalize(self):
        self.logger.debug("finalize called")


m = QueueSource(self._queue)
def my_callback(command, *args):
    self.logger.debug("my_callback: args: " + str(command) + ", " + str(args))
    self.logger.debug("my_callback: thread: " + str(threading.currentThread()))
    if command == self.PLAY:
        self._player.play()
    elif command == self.STOP:
        self._player.stop()
    elif command == self.SKIP:
        self._player.skip()

m.set_callback(my_callback, mainloop)
m.attach()

And here's Pygame:

class PygameEventSource(gobject.Source):
    def __init__(self):
        gobject.Source.__init__(self)
        self.logger = loghelper.get_logger(self)

    def prepare(self):
        return False

    def check(self):
        try:
            pygame.event.pump()
            p = pygame.event.peek(
                [KEYUP, KEYDOWN, VIDEOEXPOSE, VIDEORESIZE, NUMEVENTS, QUIT,
                 ACTIVEEVENT, USEREVENT])
            return p
        except Exception, ex:
            self.logger.error("Caught an exception while checking for events",
                              exc_info=True)

    def dispatch(self, callback, args):
        if callback is not None:
            try:
                e = pygame.event.poll()
                v = callback(e, *args)
                return v
            except (KeyboardInterrupt, SystemExit):
                self.logger.warn(
                    "Caught a terminating exception while dispatching to the "
                    "callback, re-raising", exc_info=True)
                pygame.display.quit()
                raise
            except Exception, ex:
                self.logger.error(
                    "Caught an exception while dispatching to the callback",
                    exc_info=True)
        return True

    def finalize(self):
        pass

def my_callback(self, event, *args):
    if event.type == QUIT:
        sys.exit()
    if event.type == KEYDOWN:
        self.logger.debug("my_callback: keydown: event.key: " + str(event.key))
        if event.key in (K_q,):
            sys.exit()
        elif event.key in (K_n, K_SPACE):
            self._player.skip()
        elif event.key in (K_p,):
            if self._player.playing:
                self._player.stop()
            else:
                self._player.play()
    
    return True


def start_mainloop(self, mainloop):
    self.logger.debug("Starting mainloop")
    if not self._started:
        s = PygameEventSource()
        s.set_callback(self.my_callback, mainloop)
        s.attach()
    return

© Juri Pakaste 2023