Ticket #4558: gi-support.patch

File gi-support.patch, 14.4 KB (added by dobey, 5 years ago)

Patch to add gireactor.py

  • twisted/internet/gireactor.py

    === added file 'twisted/internet/gireactor.py'
     
     1# -*- test-case-name: twisted.internet.test -*-
     2# Copyright (c) Twisted Matrix Laboratories.
     3# Copyright (c) 2011 Canonical Ltd.
     4# See LICENSE for details.
     5
     6"""
     7This module provides support for Twisted to interact with the glib/gtk2/gtk3
     8mainloop.
     9
     10In order to use this support, simply do the following::
     11
     12    |  from twisted.internet import gireactor
     13    |  gireactor.install()
     14
     15Then use twisted.internet APIs as usual.  The other methods here are not
     16intended to be called directly.
     17
     18When installing the reactor, you can choose whether to use the glib
     19event loop or the GTK+ event loop which is based on it but adds GUI
     20integration.
     21"""
     22
     23import signal
     24import sys
     25
     26if 'gobject' in sys.modules:
     27    import glib as GLib
     28else:
     29    from gi.repository import GLib
     30    # This is some nasty junk right here. But it should stop SEGFAULTs
     31    sys.modules['glib'] = None
     32    sys.modules['gobject'] = None
     33    sys.modules['gio'] = None
     34    sys.modules['gtk'] = None
     35
     36from twisted.internet import base, posixbase, selectreactor
     37from twisted.internet.interfaces import IReactorFDSet
     38from twisted.python import log, runtime
     39from twisted.python.compat import set
     40from zope.interface import implements
     41
     42
     43GLib.threads_init()
     44
     45POLL_DISCONNECTED = (GLib.IOCondition.HUP | GLib.IOCondition.ERR |
     46                     GLib.IOCondition.NVAL)
     47
     48# glib's iochannel sources won't tell us about any events that we haven't
     49# asked for, even if those events aren't sensible inputs to the poll()
     50# call.
     51INFLAGS = GLib.IOCondition.IN | POLL_DISCONNECTED
     52OUTFLAGS = GLib.IOCondition.OUT | POLL_DISCONNECTED
     53
     54
     55class _GISignalMixin(object):
     56
     57    if runtime.platformType == 'posix':
     58
     59        def _handleSignals(self):
     60            # Let the base class do its thing, but pygtk is probably
     61            # going to stomp on us so go beyond that and set up some
     62            # signal handling which pygtk won't mess with.  This would
     63            # be better done by letting this reactor select a
     64            # different implementation of installHandler for
     65            # _SIGCHLDWaker to use.  Then, at least, we could fall
     66            # back to our extension module.  See #4286.
     67            from twisted.internet.process import (
     68                reapAllProcesses as _reapAllProcesses)
     69            base._SignalReactorMixin._handleSignals(self)
     70            signal.signal(signal.SIGCHLD,
     71                          lambda *a: self.callFromThread(_reapAllProcesses))
     72            if getattr(signal, "siginterrupt", None) is not None:
     73                signal.siginterrupt(signal.SIGCHLD, False)
     74            # Like the base, reap processes now in case a process
     75            # exited before the handlers above were installed.
     76            _reapAllProcesses()
     77
     78
     79class _GIWaker(posixbase._UnixWaker):
     80    """
     81    Run scheduled events after waking up.
     82    """
     83
     84    def doRead(self):
     85        posixbase._UnixWaker.doRead(self)
     86        self.reactor._simulate()
     87
     88
     89class GIReactor(_GISignalMixin,
     90                posixbase.PosixReactorBase, posixbase._PollLikeMixin):
     91    """
     92    GObject event loop reactor.
     93
     94    @ivar _sources: A dictionary mapping L{FileDescriptor} instances to
     95        GSource handles.
     96
     97    @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
     98        reading.
     99
     100    @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
     101        writing.
     102
     103    @ivar _simtag: A GSource handle for the next L{simulate} call.
     104    """
     105    implements(IReactorFDSet)
     106
     107    _POLL_DISCONNECTED = POLL_DISCONNECTED
     108    _POLL_IN = GLib.IOCondition.IN
     109    _POLL_OUT = GLib.IOCondition.OUT
     110
     111    # Install a waker that knows it needs to call C{_simulate} in order to run
     112    # callbacks queued from a thread:
     113    _wakerFactory = _GIWaker
     114
     115    def __init__(self, useGtk=False):
     116        self._simtag = None
     117        self._reads = set()
     118        self._writes = set()
     119        self._sources = {}
     120        posixbase.PosixReactorBase.__init__(self)
     121
     122        if useGtk:
     123            if 'gobject' in sys.modules:
     124                import gtk as Gtk
     125            else:
     126                from gi.repository import Gtk
     127
     128            def mainquit():
     129                if Gtk.main_level():
     130                    Gtk.main_quit()
     131
     132            self.__pending = Gtk.events_pending
     133            self.__iteration = Gtk.main_iteration
     134            self.__crash = mainquit
     135            self.__run = Gtk.main
     136        else:
     137            self.context = GLib.main_context_default()
     138            self.__pending = self.context.pending
     139            self.__iteration = self.context.iteration
     140            self.loop = GLib.MainLoop()
     141            self.__crash = lambda: GLib.idle_add(self.loop.quit)
     142            self.__run = self.loop.run
     143
     144    # The input_add function in pygtk1 checks for objects with a
     145    # 'fileno' method and, if present, uses the result of that method
     146    # as the input source. The pygtk2 input_add does not do this. The
     147    # function below replicates the pygtk1 functionality.
     148
     149    # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
     150    # g_io_add_watch() takes different condition bitfields than
     151    # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
     152    # bug.
     153    def input_add(self, source, condition, callback):
     154        if hasattr(source, 'fileno'):
     155            # handle python objects
     156            def wrapper(source, condition, real_s=source, real_cb=callback):
     157                return real_cb(real_s, condition)
     158            return GLib.io_add_watch(source.fileno(), condition, wrapper)
     159        else:
     160            return GLib.io_add_watch(source, condition, callback)
     161
     162    def _ioEventCallback(self, source, condition):
     163        """
     164        Called by event loop when an I/O event occurs.
     165        """
     166        log.callWithLogger(
     167            source, self._doReadOrWrite, source, source, condition)
     168        return True  # True = don't auto-remove the source
     169
     170    def _add(self, source, primary, other, primaryFlag, otherFlag):
     171        """
     172        Add the given L{FileDescriptor} for monitoring either for reading or
     173        writing. If the file is already monitored for the other operation, we
     174        delete the previous registration and re-register it for both reading
     175        and writing.
     176        """
     177        if source in primary:
     178            return
     179        flags = primaryFlag
     180        if source in other:
     181            GLib.source_remove(self._sources[source])
     182            flags |= otherFlag
     183        self._sources[source] = self.input_add(
     184            source, flags, self._ioEventCallback)
     185        primary.add(source)
     186
     187    def addReader(self, reader):
     188        """
     189        Add a L{FileDescriptor} for monitoring of data available to read.
     190        """
     191        self._add(reader, self._reads, self._writes, INFLAGS, OUTFLAGS)
     192
     193    def addWriter(self, writer):
     194        """
     195        Add a L{FileDescriptor} for monitoring ability to write data.
     196        """
     197        self._add(writer, self._writes, self._reads, OUTFLAGS, INFLAGS)
     198
     199    def getReaders(self):
     200        """
     201        Retrieve the list of current L{FileDescriptor} monitored for reading.
     202        """
     203        return list(self._reads)
     204
     205    def getWriters(self):
     206        """
     207        Retrieve the list of current L{FileDescriptor} monitored for writing.
     208        """
     209        return list(self._writes)
     210
     211    def removeAll(self):
     212        """
     213        Remove monitoring for all registered L{FileDescriptor}s.
     214        """
     215        return self._removeAll(self._reads, self._writes)
     216
     217    def _remove(self, source, primary, other, flags):
     218        """
     219        Remove monitoring the given L{FileDescriptor} for either reading or
     220        writing. If it's still monitored for the other operation, we
     221        re-register the L{FileDescriptor} for only that operation.
     222        """
     223        if source not in primary:
     224            return
     225        GLib.source_remove(self._sources[source])
     226        primary.remove(source)
     227        if source in other:
     228            self._sources[source] = self.input_add(
     229                source, flags, self._ioEventCallback)
     230        else:
     231            self._sources.pop(source)
     232
     233    def removeReader(self, reader):
     234        """
     235        Stop monitoring the given L{FileDescriptor} for reading.
     236        """
     237        self._remove(reader, self._reads, self._writes, OUTFLAGS)
     238
     239    def removeWriter(self, writer):
     240        """
     241        Stop monitoring the given L{FileDescriptor} for writing.
     242        """
     243        self._remove(writer, self._writes, self._reads, INFLAGS)
     244
     245    def iterate(self, delay=0):
     246        """
     247        One iteration of the event loop, for trial's use.
     248
     249        This is not used for actual reactor runs.
     250        """
     251        self.runUntilCurrent()
     252        while self.__pending():
     253            self.__iteration(0)
     254
     255    def crash(self):
     256        """
     257        Crash the reactor.
     258        """
     259        posixbase.PosixReactorBase.crash(self)
     260        self.__crash()
     261
     262    def stop(self):
     263        """
     264        Stop the reactor.
     265        """
     266        posixbase.PosixReactorBase.stop(self)
     267        # The base implementation only sets a flag, to ensure shutting down is
     268        # not reentrant. Unfortunately, this flag is not meaningful to the
     269        # gobject event loop. We therefore call wakeUp() to ensure the event
     270        # loop will call back into Twisted once this iteration is done. This
     271        # will result in self.runUntilCurrent() being called, where the stop
     272        # flag will trigger the actual shutdown process, eventually calling
     273        # crash() which will do the actual gobject event loop shutdown.
     274        self.wakeUp()
     275
     276    def run(self, installSignalHandlers=True):
     277        """
     278        Run the reactor.
     279        """
     280        self.callWhenRunning(self._reschedule)
     281        self.startRunning(installSignalHandlers=installSignalHandlers)
     282        if self._started:
     283            self.__run()
     284
     285    def callLater(self, *args, **kwargs):
     286        """
     287        Schedule a C{DelayedCall}.
     288        """
     289        result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs)
     290        # Make sure we'll get woken up at correct time to handle this new
     291        # scheduled call:
     292        self._reschedule()
     293        return result
     294
     295    def _reschedule(self):
     296        """
     297        Schedule a glib timeout for C{_simulate}.
     298        """
     299        if self._simtag is not None:
     300            GLib.source_remove(self._simtag)
     301            self._simtag = None
     302        timeout = self.timeout()
     303        if timeout is not None:
     304            self._simtag = GLib.timeout_add(int(timeout * 1000),
     305                                            self._simulate)
     306
     307    def _simulate(self):
     308        """
     309        Run timers, and then reschedule glib timeout for next scheduled event.
     310        """
     311        self.runUntilCurrent()
     312        self._reschedule()
     313
     314
     315class PortableGIReactor(_GISignalMixin, selectreactor.SelectReactor):
     316    """
     317    Portable GObject Introspection event loop reactor.
     318    """
     319    def __init__(self, useGtk=False):
     320        self._simtag = None
     321        selectreactor.SelectReactor.__init__(self)
     322
     323        if useGtk:
     324            if 'gobject' in sys.modules:
     325                import gtk as Gtk
     326            else:
     327                from gi.repository import Gtk
     328
     329            def mainquit():
     330                if Gtk.main_level():
     331                    Gtk.main_quit()
     332
     333            self.__crash = mainquit
     334            self.__run = Gtk.main
     335        else:
     336            self.loop = GLib.MainLoop()
     337            self.__crash = lambda: GLib.idle_add(self.loop.quit)
     338            self.__run = self.loop.run
     339
     340    def crash(self):
     341        selectreactor.SelectReactor.crash(self)
     342        self.__crash()
     343
     344    def run(self, installSignalHandlers=True):
     345        self.startRunning(installSignalHandlers=installSignalHandlers)
     346        GLib.idle_add(self.simulate)
     347        self.__run()
     348
     349    def simulate(self):
     350        """
     351        Run simulation loops and reschedule callbacks.
     352        """
     353        if self._simtag is not None:
     354            GLib.source_remove(self._simtag)
     355        self.iterate()
     356        timeout = min(self.timeout(), 0.1)
     357        if timeout is None:
     358            timeout = 0.1
     359        self._simtag = GLib.timeout_add(int(timeout * 1000), self.simulate)
     360
     361
     362def install(useGtk=False):
     363    """
     364    Configure the twisted mainloop to be run inside the glib mainloop.
     365
     366    @param useGtk: should GTK+ rather than glib event loop be
     367        used (this will be slightly slower but does support GUI).
     368    """
     369    if runtime.platform.getType() == 'posix':
     370        reactor = GIReactor(useGtk=useGtk)
     371    else:
     372        reactor = PortableGIReactor(useGtk=useGtk)
     373
     374    from twisted.internet.main import installReactor
     375    installReactor(reactor)
     376    return reactor
     377
     378
     379__all__ = ['install']
  • twisted/internet/test/reactormixins.py

    === modified file 'twisted/internet/test/reactormixins.py'
    class ReactorBuilder: 
    5656        # it's not _really_ worth it to support on other platforms,
    5757        # since no one really wants to use it on other platforms.
    5858        _reactors.extend([
     59                "twisted.internet.gireactor.PortableGIReactor",
    5960                "twisted.internet.gtk2reactor.PortableGtkReactor",
    6061                "twisted.internet.win32eventreactor.Win32Reactor",
    6162                "twisted.internet.iocpreactor.reactor.IOCPReactor"])
    6263    else:
    6364        _reactors.extend([
     65                "twisted.internet.gireactor.GIReactor",
    6466                "twisted.internet.glib2reactor.Glib2Reactor",
    6567                "twisted.internet.gtk2reactor.Gtk2Reactor",
    6668                "twisted.internet.kqreactor.KQueueReactor"])
  • twisted/plugins/twisted_reactors.py

    === modified file 'twisted/plugins/twisted_reactors.py'
    select = Reactor( 
    1111    'select', 'twisted.internet.selectreactor', 'select(2)-based reactor.')
    1212wx = Reactor(
    1313    'wx', 'twisted.internet.wxreactor', 'wxPython integration reactor.')
     14gi = Reactor(
     15    'gi', 'twisted.internet.gireactor', 'GObject Introspection integration reactor.')
    1416gtk = Reactor(
    1517    'gtk', 'twisted.internet.gtkreactor', 'Gtk1 integration reactor.')
    1618gtk2 = Reactor(