Ticket #4558: gi-support.patch

File gi-support.patch, 14.4 KB (added by dobey, 3 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(