Ticket #4558: gi-support.2.patch

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

Updated patch per review comments

  • twisted/internet/_glibbase.py

    === added file 'twisted/internet/_glibbase.py'
     
     1# -*- test-case-name: twisted.internet.test -*-
     2# Copyright (c) Twisted Matrix Laboratories.
     3# Copyright (c) 2011-2012 Canonical Ltd.
     4# See LICENSE for details.
     5"""
     6This module provides base support for Twisted to interact with the glib/gtk
     7mainloops.
     8
     9The classes in this module should not be used directly, but rather you should
     10import gireactor or gtk3reactor for GObject Introspection based applications,
     11or glib2reactor or gtk2reactor for applications using legacy static bindings.
     12"""
     13
     14import signal
     15
     16from twisted.internet import base, posixbase, selectreactor
     17from twisted.internet.interfaces import IReactorFDSet
     18from twisted.python import log, runtime
     19from twisted.python.compat import set
     20from zope.interface import implements
     21
     22class GlibSignalMixin(object):
     23
     24    if runtime.platformType == 'posix':
     25
     26        def _handleSignals(self):
     27            # Let the base class do its thing, but pygtk is probably
     28            # going to stomp on us so go beyond that and set up some
     29            # signal handling which pygtk won't mess with.  This would
     30            # be better done by letting this reactor select a
     31            # different implementation of installHandler for
     32            # _SIGCHLDWaker to use.  Then, at least, we could fall
     33            # back to our extension module.  See #4286.
     34            from twisted.internet.process import (
     35                reapAllProcesses as _reapAllProcesses)
     36            base._SignalReactorMixin._handleSignals(self)
     37            signal.signal(signal.SIGCHLD,
     38                          lambda *a: self.callFromThread(_reapAllProcesses))
     39            if getattr(signal, "siginterrupt", None) is not None:
     40                signal.siginterrupt(signal.SIGCHLD, False)
     41            # Like the base, reap processes now in case a process
     42            # exited before the handlers above were installed.
     43            _reapAllProcesses()
     44
     45
     46class GlibWaker(posixbase._UnixWaker):
     47    """
     48    Run scheduled events after waking up.
     49    """
     50
     51    def doRead(self):
     52        posixbase._UnixWaker.doRead(self)
     53        self.reactor._simulate()
     54
     55
     56class GlibReactorBase(GlibSignalMixin,
     57                      posixbase.PosixReactorBase, posixbase._PollLikeMixin):
     58    """
     59    GObject event loop reactor.
     60
     61    @ivar _sources: A dictionary mapping L{FileDescriptor} instances to
     62        GSource handles.
     63
     64    @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
     65        reading.
     66
     67    @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
     68        writing.
     69
     70    @ivar _simtag: A GSource handle for the next L{simulate} call.
     71    """
     72    implements(IReactorFDSet)
     73
     74    # Install a waker that knows it needs to call C{_simulate} in order to run
     75    # callbacks queued from a thread:
     76    _wakerFactory = GlibWaker
     77
     78    def __init__(self):
     79        self._simtag = None
     80        self._reads = set()
     81        self._writes = set()
     82        self._sources = {}
     83        posixbase.PosixReactorBase.__init__(self)
     84
     85    def input_add(self, source, condition, callback):
     86        """This is a stub to be implemented by inheriting classes."""
     87        raise NotImplementedError()
     88
     89    def _ioEventCallback(self, source, condition):
     90        """
     91        Called by event loop when an I/O event occurs.
     92        """
     93        log.callWithLogger(
     94            source, self._doReadOrWrite, source, source, condition)
     95        return True  # True = don't auto-remove the source
     96
     97    def _add(self, source, primary, other, primaryFlag, otherFlag):
     98        """
     99        Add the given L{FileDescriptor} for monitoring either for reading or
     100        writing. If the file is already monitored for the other operation, we
     101        delete the previous registration and re-register it for both reading
     102        and writing.
     103        """
     104        if source in primary:
     105            return
     106        flags = primaryFlag
     107        if source in other:
     108            self._source_remove(self._sources[source])
     109            flags |= otherFlag
     110        self._sources[source] = self.input_add(
     111            source, flags, self._ioEventCallback)
     112        primary.add(source)
     113
     114    def addReader(self, reader):
     115        """
     116        Add a L{FileDescriptor} for monitoring of data available to read.
     117        """
     118        self._add(reader, self._reads, self._writes,
     119                  self.INFLAGS, self.OUTFLAGS)
     120
     121    def addWriter(self, writer):
     122        """
     123        Add a L{FileDescriptor} for monitoring ability to write data.
     124        """
     125        self._add(writer, self._writes, self._reads,
     126                  self.OUTFLAGS, self.INFLAGS)
     127
     128    def getReaders(self):
     129        """
     130        Retrieve the list of current L{FileDescriptor} monitored for reading.
     131        """
     132        return list(self._reads)
     133
     134    def getWriters(self):
     135        """
     136        Retrieve the list of current L{FileDescriptor} monitored for writing.
     137        """
     138        return list(self._writes)
     139
     140    def removeAll(self):
     141        """
     142        Remove monitoring for all registered L{FileDescriptor}s.
     143        """
     144        return self._removeAll(self._reads, self._writes)
     145
     146    def _remove(self, source, primary, other, flags):
     147        """
     148        Remove monitoring the given L{FileDescriptor} for either reading or
     149        writing. If it's still monitored for the other operation, we
     150        re-register the L{FileDescriptor} for only that operation.
     151        """
     152        if source not in primary:
     153            return
     154        self._source_remove(self._sources[source])
     155        primary.remove(source)
     156        if source in other:
     157            self._sources[source] = self.input_add(
     158                source, flags, self._ioEventCallback)
     159        else:
     160            self._sources.pop(source)
     161
     162    def removeReader(self, reader):
     163        """
     164        Stop monitoring the given L{FileDescriptor} for reading.
     165        """
     166        self._remove(reader, self._reads, self._writes, self.OUTFLAGS)
     167
     168    def removeWriter(self, writer):
     169        """
     170        Stop monitoring the given L{FileDescriptor} for writing.
     171        """
     172        self._remove(writer, self._writes, self._reads, self.INFLAGS)
     173
     174    def iterate(self, delay=0):
     175        """
     176        One iteration of the event loop, for trial's use.
     177
     178        This is not used for actual reactor runs.
     179        """
     180        self.runUntilCurrent()
     181        while self._pending():
     182            self._iteration(0)
     183
     184    def crash(self):
     185        """
     186        Crash the reactor.
     187        """
     188        posixbase.PosixReactorBase.crash(self)
     189        self._crash()
     190
     191    def stop(self):
     192        """
     193        Stop the reactor.
     194        """
     195        posixbase.PosixReactorBase.stop(self)
     196        # The base implementation only sets a flag, to ensure shutting down is
     197        # not reentrant. Unfortunately, this flag is not meaningful to the
     198        # gobject event loop. We therefore call wakeUp() to ensure the event
     199        # loop will call back into Twisted once this iteration is done. This
     200        # will result in self.runUntilCurrent() being called, where the stop
     201        # flag will trigger the actual shutdown process, eventually calling
     202        # crash() which will do the actual gobject event loop shutdown.
     203        self.wakeUp()
     204
     205    def run(self, installSignalHandlers=True):
     206        """
     207        Run the reactor.
     208        """
     209        self.callWhenRunning(self._reschedule)
     210        self.startRunning(installSignalHandlers=installSignalHandlers)
     211        if self._started:
     212            self._run()
     213
     214    def callLater(self, *args, **kwargs):
     215        """
     216        Schedule a C{DelayedCall}.
     217        """
     218        result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs)
     219        # Make sure we'll get woken up at correct time to handle this new
     220        # scheduled call:
     221        self._reschedule()
     222        return result
     223
     224    def _reschedule(self):
     225        """
     226        Schedule a glib timeout for C{_simulate}.
     227        """
     228        if self._simtag is not None:
     229            self._source_remove(self._simtag)
     230            self._simtag = None
     231        timeout = self.timeout()
     232        if timeout is not None:
     233            self._simtag = self._timeout_add(int(timeout * 1000),
     234                                             self._simulate)
     235
     236    def _simulate(self):
     237        """
     238        Run timers, and then reschedule glib timeout for next scheduled event.
     239        """
     240        self.runUntilCurrent()
     241        self._reschedule()
     242
     243
     244class PortableGlibReactorBase(GlibSignalMixin, selectreactor.SelectReactor):
     245    """
     246    Portable GObject event loop reactor.
     247    """
     248    def __init__(self):
     249        self._simtag = None
     250        selectreactor.SelectReactor.__init__(self)
     251
     252    def crash(self):
     253        selectreactor.SelectReactor.crash(self)
     254        self._crash()
     255
     256    def run(self, installSignalHandlers=True):
     257        self.startRunning(installSignalHandlers=installSignalHandlers)
     258        self.idle_add(self.simulate)
     259        self._run()
     260
     261    def simulate(self):
     262        """
     263        Run simulation loops and reschedule callbacks.
     264        """
     265        if self._simtag is not None:
     266            self._source_remove(self._simtag)
     267        self.iterate()
     268        timeout = min(self.timeout(), 0.1)
     269        if timeout is None:
     270            timeout = 0.1
     271        self._simtag = self._timeout_add(int(timeout * 1000), self.simulate)
  • 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-2012 Canonical Ltd.
     4# See LICENSE for details.
     5
     6"""
     7This module provides support for Twisted to interact with the glib/gtk3
     8mainloop via GObject Introspection.
     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 sys
     24
     25if 'gobject' in sys.modules:
     26    raise ImportError(('Introspected and static bindings must not be mixed.'
     27                       ' Use glib2reactor or gtk2reactor instead.'))
     28
     29
     30from gi.repository import GLib
     31# We need to override sys.modules with these to prevent imports.
     32# This is required, as importing these can result in SEGFAULTs.
     33sys.modules['glib'] = None
     34sys.modules['gobject'] = None
     35sys.modules['gio'] = None
     36sys.modules['gtk'] = None
     37
     38from twisted.internet import _glibbase
     39from twisted.python import runtime
     40
     41GLib.threads_init()
     42
     43
     44class GIReactor(_glibbase.GlibReactorBase):
     45    """
     46    GObject event loop reactor.
     47
     48    @ivar _sources: A dictionary mapping L{FileDescriptor} instances to
     49        GSource handles.
     50
     51    @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
     52        reading.
     53
     54    @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
     55        writing.
     56
     57    @ivar _simtag: A GSource handle for the next L{simulate} call.
     58    """
     59    _POLL_DISCONNECTED = (GLib.IOCondition.HUP | GLib.IOCondition.ERR |
     60                          GLib.IOCondition.NVAL)
     61    _POLL_IN = GLib.IOCondition.IN
     62    _POLL_OUT = GLib.IOCondition.OUT
     63
     64    # glib's iochannel sources won't tell us about any events that we haven't
     65    # asked for, even if those events aren't sensible inputs to the poll()
     66    # call.
     67    INFLAGS = _POLL_IN | _POLL_DISCONNECTED
     68    OUTFLAGS = _POLL_OUT | _POLL_DISCONNECTED
     69
     70    def __init__(self, useGtk=False):
     71        _glibbase.GlibReactorBase.__init__(self)
     72
     73        self._source_remove = GLib.source_remove
     74        self._timeout_add = GLib.timeout_add
     75
     76        if useGtk:
     77            from gi.repository import Gtk
     78
     79            self._pending = Gtk.events_pending
     80            self._iteration = Gtk.main_iteration_do
     81            self._crash = Gtk.main_quit
     82            self._run = Gtk.main
     83        else:
     84            self.context = GLib.main_context_default()
     85            self._pending = self.context.pending
     86            self._iteration = self.context.iteration
     87            self.loop = GLib.MainLoop()
     88            self._crash = lambda: GLib.idle_add(self.loop.quit)
     89            self._run = self.loop.run
     90
     91    # The input_add function in pygtk1 checks for objects with a
     92    # 'fileno' method and, if present, uses the result of that method
     93    # as the input source. The pygtk2 input_add does not do this. The
     94    # function below replicates the pygtk1 functionality.
     95
     96    # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
     97    # g_io_add_watch() takes different condition bitfields than
     98    # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
     99    # bug.
     100    def input_add(self, source, condition, callback):
     101        if hasattr(source, 'fileno'):
     102            # handle python objects
     103            def wrapper(source, condition, real_s=source, real_cb=callback):
     104                return real_cb(real_s, condition)
     105            return GLib.io_add_watch(source.fileno(), condition, wrapper)
     106        else:
     107            return GLib.io_add_watch(source, condition, callback)
     108
     109
     110class PortableGIReactor(_glibbase.PortableGlibReactorBase):
     111    """
     112    Portable GObject Introspection event loop reactor.
     113    """
     114    def __init__(self, useGtk=False):
     115        _glibbase.PortableGlibReactorBase.__init__(self)
     116
     117        self._source_remove = GLib.source_remove
     118        self._idle_add = GLib.idle_add
     119        self._timeout_add = GLib.timeout_add
     120
     121        if useGtk:
     122            from gi.repository import Gtk
     123
     124            self._crash = Gtk.main_quit
     125            self._run = Gtk.main
     126        else:
     127            self.loop = GLib.MainLoop()
     128            self._crash = lambda: GLib.idle_add(self.loop.quit)
     129            self._run = self.loop.run
     130
     131
     132def install(useGtk=False):
     133    """
     134    Configure the twisted mainloop to be run inside the glib mainloop.
     135
     136    @param useGtk: should GTK+ rather than glib event loop be
     137        used (this will be slightly slower but does support GUI).
     138    """
     139    if runtime.platform.getType() == 'posix':
     140        reactor = GIReactor(useGtk=useGtk)
     141    else:
     142        reactor = PortableGIReactor(useGtk=useGtk)
     143
     144    from twisted.internet.main import installReactor
     145    installReactor(reactor)
     146    return reactor
     147
     148
     149__all__ = ['install']
  • twisted/internet/gtk2reactor.py

    === modified file 'twisted/internet/gtk2reactor.py'
    integration. 
    2121"""
    2222
    2323# System Imports
    24 import sys, signal
     24import sys
    2525
    26 from zope.interface import implements
     26if 'gi' in sys.modules:
     27    raise ImportError(('Introspected and static bindings must not be mixed.'
     28                       ' Use twisted.internet.gireactor instead.'))
     29
     30# Disable gi imports to avoid potential problems.
     31sys.modules['gi'] = None
    2732
    2833try:
    2934    if not hasattr(sys, 'frozen'):
    if hasattr(gobject, "threads_init"): 
    4045    gobject.threads_init()
    4146
    4247# Twisted Imports
    43 from twisted.python import log, runtime
    44 from twisted.python.compat import set
    45 from twisted.internet.interfaces import IReactorFDSet
    46 from twisted.internet import base, posixbase, selectreactor
    47 
    48 POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
    49 
    50 # glib's iochannel sources won't tell us about any events that we haven't
    51 # asked for, even if those events aren't sensible inputs to the poll()
    52 # call.
    53 INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
    54 OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
    55 
    56 
    57 
    58 def _our_mainquit():
    59     # XXX: gtk.main_quit() (which is used for crash()) raises an exception if
    60     # gtk.main_level() == 0; however, all the tests freeze if we use this
    61     # function to stop the reactor.  what gives?  (I believe this may have been
    62     # a stupid mistake where I forgot to import gtk here... I will remove this
    63     # comment if the tests pass)
    64     import gtk
    65     if gtk.main_level():
    66         gtk.main_quit()
    67 
    68 
    69 
    70 class _Gtk2SignalMixin(object):
    71     if runtime.platformType == 'posix':
    72         def _handleSignals(self):
    73             # Let the base class do its thing, but pygtk is probably
    74             # going to stomp on us so go beyond that and set up some
    75             # signal handling which pygtk won't mess with.  This would
    76             # be better done by letting this reactor select a
    77             # different implementation of installHandler for
    78             # _SIGCHLDWaker to use.  Then, at least, we could fall
    79             # back to our extension module.  See #4286.
    80             from twisted.internet.process import reapAllProcesses as _reapAllProcesses
    81             base._SignalReactorMixin._handleSignals(self)
    82             signal.signal(signal.SIGCHLD, lambda *a: self.callFromThread(_reapAllProcesses))
    83             if getattr(signal, "siginterrupt", None) is not None:
    84                 signal.siginterrupt(signal.SIGCHLD, False)
    85             # Like the base, reap processes now in case a process
    86             # exited before the handlers above were installed.
    87             _reapAllProcesses()
    88 
     48from twisted.internet import _glibbase
     49from twisted.python import runtime
    8950
    9051
    91 class _Gtk2Waker(posixbase._UnixWaker):
    92     """
    93     Run scheduled events after waking up.
    94     """
    95 
    96     def doRead(self):
    97         posixbase._UnixWaker.doRead(self)
    98         self.reactor._simulate()
    99 
    100 
    101 
    102 class Gtk2Reactor(_Gtk2SignalMixin, posixbase.PosixReactorBase, posixbase._PollLikeMixin):
     52class Gtk2Reactor(_glibbase.GlibReactorBase):
    10353    """
    10454    GTK+-2 event loop reactor.
    10555
    class Gtk2Reactor(_Gtk2SignalMixin, posi 
    13080
    13181    @ivar _simtag: A gtk timeout handle for the next L{_simulate} call.
    13282    """
    133     implements(IReactorFDSet)
    134 
    135     _POLL_DISCONNECTED = POLL_DISCONNECTED
     83    _POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
    13684    _POLL_IN = gobject.IO_IN
    13785    _POLL_OUT = gobject.IO_OUT
    13886
    139     # Install a waker that knows it needs to call C{_simulate} in order to run
    140     # callbacks queued from a thread:
    141     _wakerFactory = _Gtk2Waker
     87    # glib's iochannel sources won't tell us about any events that we haven't
     88    # asked for, even if those events aren't sensible inputs to the poll()
     89    # call.
     90    INFLAGS = _POLL_IN | _POLL_DISCONNECTED
     91    OUTFLAGS = _POLL_OUT | _POLL_DISCONNECTED
    14292
    14393    def __init__(self, useGtk=True):
    144         self._simtag = None
    145         self._reads = set()
    146         self._writes = set()
    147         self._sources = {}
    148         posixbase.PosixReactorBase.__init__(self)
     94        _glibbase.GlibReactorBase.__init__(self)
     95
     96        self._source_remove = gobject.source_remove
     97        self._timeout_add = gobject.timeout_add
     98
    14999        # pre 2.3.91 the glib iteration and mainloop functions didn't release
    150100        # global interpreter lock, thus breaking thread and signal support.
    151101        if getattr(gobject, "pygtk_version", ()) >= (2, 3, 91) and not useGtk:
    152102            self.context = gobject.main_context_default()
    153             self.__pending = self.context.pending
    154             self.__iteration = self.context.iteration
     103            self._pending = self.context.pending
     104            self._iteration = self.context.iteration
    155105            self.loop = gobject.MainLoop()
    156             self.__crash = self.loop.quit
    157             self.__run = self.loop.run
     106            self._crash = self.loop.quit
     107            self._run = self.loop.run
    158108        else:
    159109            import gtk
    160             self.__pending = gtk.events_pending
    161             self.__iteration = gtk.main_iteration
    162             self.__crash = _our_mainquit
    163             self.__run = gtk.main
     110
     111            def mainquit():
     112                if gtk.main_level():
     113                    gtk.main_quit()
     114
     115            self._pending = gtk.events_pending
     116            self._iteration = gtk.main_iteration
     117            self._crash = mainquit
     118            self._run = gtk.main
    164119
    165120
    166121    # The input_add function in pygtk1 checks for objects with a
    class Gtk2Reactor(_Gtk2SignalMixin, posi 
    182137            return gobject.io_add_watch(source, condition, callback)
    183138
    184139
    185     def _ioEventCallback(self, source, condition):
    186         """
    187         Called by event loop when an I/O event occurs.
    188         """
    189         log.callWithLogger(
    190             source, self._doReadOrWrite, source, source, condition)
    191         return 1 # 1=don't auto-remove the source
    192 
    193 
    194     def _add(self, source, primary, other, primaryFlag, otherFlag):
    195         """
    196         Add the given L{FileDescriptor} for monitoring either for reading or
    197         writing. If the file is already monitored for the other operation, we
    198         delete the previous registration and re-register it for both reading
    199         and writing.
    200         """
    201         if source in primary:
    202             return
    203         flags = primaryFlag
    204         if source in other:
    205             gobject.source_remove(self._sources[source])
    206             flags |= otherFlag
    207         self._sources[source] = self.input_add(
    208             source, flags, self._ioEventCallback)
    209         primary.add(source)
    210 
    211 
    212     def addReader(self, reader):
    213         """
    214         Add a L{FileDescriptor} for monitoring of data available to read.
    215         """
    216         self._add(reader, self._reads, self._writes, INFLAGS, OUTFLAGS)
    217 
    218 
    219     def addWriter(self, writer):
    220         """
    221         Add a L{FileDescriptor} for monitoring ability to write data.
    222         """
    223         self._add(writer, self._writes, self._reads, OUTFLAGS, INFLAGS)
    224 
    225 
    226     def getReaders(self):
    227         """
    228         Retrieve the list of current L{FileDescriptor} monitored for reading.
    229         """
    230         return list(self._reads)
    231 
    232 
    233     def getWriters(self):
    234         """
    235         Retrieve the list of current L{FileDescriptor} monitored for writing.
    236         """
    237         return list(self._writes)
    238 
    239 
    240     def removeAll(self):
    241         """
    242         Remove monitoring for all registered L{FileDescriptor}s.
    243         """
    244         return self._removeAll(self._reads, self._writes)
    245 
    246 
    247     def _remove(self, source, primary, other, flags):
    248         """
    249         Remove monitoring the given L{FileDescriptor} for either reading or
    250         writing. If it's still monitored for the other operation, we
    251         re-register the L{FileDescriptor} for only that operation.
    252         """
    253         if source not in primary:
    254             return
    255         gobject.source_remove(self._sources[source])
    256         primary.remove(source)
    257         if source in other:
    258             self._sources[source] = self.input_add(
    259                 source, flags, self._ioEventCallback)
    260         else:
    261             self._sources.pop(source)
    262 
    263 
    264     def removeReader(self, reader):
    265         """
    266         Stop monitoring the given L{FileDescriptor} for reading.
    267         """
    268         self._remove(reader, self._reads, self._writes, OUTFLAGS)
    269 
    270 
    271     def removeWriter(self, writer):
    272         """
    273         Stop monitoring the given L{FileDescriptor} for writing.
    274         """
    275         self._remove(writer, self._writes, self._reads, INFLAGS)
    276 
    277 
    278     def iterate(self, delay=0):
    279         """
    280         One iteration of the event loop, for trial's use.
    281 
    282         This is not used for actual reactor runs.
    283         """
    284         self.runUntilCurrent()
    285         while self.__pending():
    286            self.__iteration(0)
    287 
    288 
    289     def crash(self):
    290         """
    291         Crash the reactor.
    292         """
    293         posixbase.PosixReactorBase.crash(self)
    294         self.__crash()
    295 
    296 
    297     def stop(self):
    298         """
    299         Stop the reactor.
    300         """
    301         posixbase.PosixReactorBase.stop(self)
    302         # The base implementation only sets a flag, to ensure shutting down is
    303         # not reentrant. Unfortunately, this flag is not meaningful to the
    304         # gobject event loop. We therefore call wakeUp() to ensure the event
    305         # loop will call back into Twisted once this iteration is done. This
    306         # will result in self.runUntilCurrent() being called, where the stop
    307         # flag will trigger the actual shutdown process, eventually calling
    308         # crash() which will do the actual gobject event loop shutdown.
    309         self.wakeUp()
    310 
    311 
    312     def run(self, installSignalHandlers=1):
    313         """
    314         Run the reactor.
    315         """
    316         self.callWhenRunning(self._reschedule)
    317         self.startRunning(installSignalHandlers=installSignalHandlers)
    318         if self._started:
    319             self.__run()
    320 
    321 
    322     def callLater(self, *args, **kwargs):
    323         """
    324         Schedule a C{DelayedCall}.
    325         """
    326         result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs)
    327         # Make sure we'll get woken up at correct time to handle this new
    328         # scheduled call:
    329         self._reschedule()
    330         return result
    331 
    332 
    333     def _reschedule(self):
    334         """
    335         Schedule a glib timeout for C{_simulate}.
    336         """
    337         if self._simtag is not None:
    338             gobject.source_remove(self._simtag)
    339             self._simtag = None
    340         timeout = self.timeout()
    341         if timeout is not None:
    342             self._simtag = gobject.timeout_add(int(timeout * 1000),
    343                                                self._simulate)
    344 
    345 
    346     def _simulate(self):
    347         """
    348         Run timers, and then reschedule glib timeout for next scheduled event.
    349         """
    350         self.runUntilCurrent()
    351         self._reschedule()
    352 
    353 
    354 
    355 class PortableGtkReactor(_Gtk2SignalMixin, selectreactor.SelectReactor):
     140class PortableGtkReactor(_glibbase.PortableGlibReactorBase):
    356141    """
    357142    Reactor that works on Windows.
    358143
    359144    Sockets aren't supported by GTK+'s input_add on Win32.
    360145    """
    361     _simtag = None
     146    def __init__(self, useGtk=False):
     147        _glibbase.PortableGlibReactorBase.__init__(self)
    362148
    363     def crash(self):
    364         selectreactor.SelectReactor.crash(self)
    365         import gtk
    366         # mainquit is deprecated in newer versions
    367         if gtk.main_level():
    368             if hasattr(gtk, 'main_quit'):
    369                 gtk.main_quit()
    370             else:
    371                 gtk.mainquit()
    372 
    373 
    374     def run(self, installSignalHandlers=1):
    375         import gtk
    376         self.startRunning(installSignalHandlers=installSignalHandlers)
    377         gobject.timeout_add(0, self.simulate)
    378         # mainloop is deprecated in newer versions
    379         if self._started:
    380             if hasattr(gtk, 'main'):
    381                 gtk.main()
    382             else:
    383                 gtk.mainloop()
    384 
    385 
    386     def simulate(self):
    387         """
    388         Run simulation loops and reschedule callbacks.
    389         """
    390         if self._simtag is not None:
    391             gobject.source_remove(self._simtag)
    392         self.iterate()
    393         timeout = min(self.timeout(), 0.01)
    394         if timeout is None:
    395             timeout = 0.01
    396         # grumble
    397         self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
     149        self._source_remove = gobject.source_remove
     150        self._idle_add = gobject.idle_add
     151        self._timeout_add = gobject.timeout_add
    398152
     153        if useGtk:
     154            import gtk
     155
     156            def mainquit():
     157                if gtk.main_level():
     158                    gtk.main_quit()
     159
     160            self._crash = mainquit
     161            self._run = gtk.main
     162        else:
     163            self.loop = gobject.MainLoop()
     164            self._crash = lambda: gobject.idle_add(self.loop.quit)
     165            self._run = self.loop.run
    399166
    400167
    401168def install(useGtk=True):
    def install(useGtk=True): 
    411178    return reactor
    412179
    413180
    414 
    415181def portableInstall(useGtk=True):
    416182    """
    417183    Configure the twisted mainloop to be run inside the gtk mainloop.
    def portableInstall(useGtk=True): 
    422188    return reactor
    423189
    424190
    425 
    426191if runtime.platform.getType() != 'posix':
    427192    install = portableInstall
    428193
    429194
    430 
    431195__all__ = ['install']
  • twisted/internet/gtk3reactor.py

    === added file 'twisted/internet/gtk3reactor.py'
     
     1
     2"""
     3This module provides support for Twisted to interact with the gtk3 mainloop.
     4This is like gi, but slightly slower and requires a working $DISPLAY.
     5
     6In order to use this support, simply do the following::
     7
     8    |  from twisted.internet import gtk3reactor
     9    |  gtk3reactor.install()
     10
     11Then use twisted.internet APIs as usual.  The other methods here are not
     12intended to be called directly.
     13
     14When installing the reactor, you can choose whether to use the glib
     15event loop or the GTK+ event loop which is based on it but adds GUI
     16integration.
     17
     18Maintainer: Itamar Shtull-Trauring
     19"""
     20
     21from twisted.internet import gireactor
     22from twisted.python import runtime
     23
     24
     25class Gtk3Reactor(gireactor.GIReactor):
     26    """
     27    The reactor using the gtk3 mainloop.
     28    """
     29
     30    def __init__(self, useGtk=True):
     31        """
     32        Override init to set the C{useGtk} flag.
     33        """
     34        gireactor.GIReactor.__init__(self, useGtk=useGtk)
     35
     36
     37class PortableGtk3Reactor(gireactor.PortableGIReactor):
     38    """
     39    Portable GTK+ 3.x reactor.
     40    """
     41    def __init__(self, useGtk=True):
     42        """
     43        Override init to set the C{useGtk} flag.
     44        """
     45        gireactor.PortableGIReactor.__init__(self, useGtk=useGtk)
     46
     47
     48def install(useGtk=True):
     49    """
     50    Configure the twisted mainloop to be run inside the glib mainloop.
     51
     52    @param useGtk: should GTK+ rather than glib event loop be
     53        used (this will be slightly slower but does support GUI).
     54    """
     55    if runtime.platform.getType() == 'posix':
     56        reactor = Gtk3Reactor(useGtk=useGtk)
     57    else:
     58        reactor = PortableGtk3Reactor(useGtk=useGtk)
     59
     60    from twisted.internet.main import installReactor
     61    installReactor(reactor)
     62    return reactor
     63
     64
     65__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",
     60                "twisted.internet.gtk3reactor.PortableGtk3Reactor",
    5961                "twisted.internet.gtk2reactor.PortableGtkReactor",
    6062                "twisted.internet.win32eventreactor.Win32Reactor",
    6163                "twisted.internet.iocpreactor.reactor.IOCPReactor"])
    6264    else:
    6365        _reactors.extend([
     66                "twisted.internet.gireactor.GIReactor",
     67                "twisted.internet.gtk3reactor.Gtk3Reactor",
    6468                "twisted.internet.glib2reactor.Glib2Reactor",
    6569                "twisted.internet.gtk2reactor.Gtk2Reactor",
    6670                "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.')
     16gtk3 = Reactor(
     17    'gtk3', 'twisted.internet.gtk3reactor', 'Gtk3 integration reactor.')
    1418gtk = Reactor(
    1519    'gtk', 'twisted.internet.gtkreactor', 'Gtk1 integration reactor.')
    1620gtk2 = Reactor(