=== added file 'twisted/internet/gireactor.py'
--- twisted/internet/gireactor.py	1970-01-01 00:00:00 +0000
+++ twisted/internet/gireactor.py	2011-12-14 19:20:59 +0000
@@ -0,0 +1,379 @@
+# -*- test-case-name: twisted.internet.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# Copyright (c) 2011 Canonical Ltd.
+# See LICENSE for details.
+
+"""
+This module provides support for Twisted to interact with the glib/gtk2/gtk3
+mainloop.
+
+In order to use this support, simply do the following::
+
+    |  from twisted.internet import gireactor
+    |  gireactor.install()
+
+Then use twisted.internet APIs as usual.  The other methods here are not
+intended to be called directly.
+
+When installing the reactor, you can choose whether to use the glib
+event loop or the GTK+ event loop which is based on it but adds GUI
+integration.
+"""
+
+import signal
+import sys
+
+if 'gobject' in sys.modules:
+    import glib as GLib
+else:
+    from gi.repository import GLib
+    # This is some nasty junk right here. But it should stop SEGFAULTs
+    sys.modules['glib'] = None
+    sys.modules['gobject'] = None
+    sys.modules['gio'] = None
+    sys.modules['gtk'] = None
+
+from twisted.internet import base, posixbase, selectreactor
+from twisted.internet.interfaces import IReactorFDSet
+from twisted.python import log, runtime
+from twisted.python.compat import set
+from zope.interface import implements
+
+
+GLib.threads_init()
+
+POLL_DISCONNECTED = (GLib.IOCondition.HUP | GLib.IOCondition.ERR |
+                     GLib.IOCondition.NVAL)
+
+# glib's iochannel sources won't tell us about any events that we haven't
+# asked for, even if those events aren't sensible inputs to the poll()
+# call.
+INFLAGS = GLib.IOCondition.IN | POLL_DISCONNECTED
+OUTFLAGS = GLib.IOCondition.OUT | POLL_DISCONNECTED
+
+
+class _GISignalMixin(object):
+
+    if runtime.platformType == 'posix':
+
+        def _handleSignals(self):
+            # Let the base class do its thing, but pygtk is probably
+            # going to stomp on us so go beyond that and set up some
+            # signal handling which pygtk won't mess with.  This would
+            # be better done by letting this reactor select a
+            # different implementation of installHandler for
+            # _SIGCHLDWaker to use.  Then, at least, we could fall
+            # back to our extension module.  See #4286.
+            from twisted.internet.process import (
+                reapAllProcesses as _reapAllProcesses)
+            base._SignalReactorMixin._handleSignals(self)
+            signal.signal(signal.SIGCHLD,
+                          lambda *a: self.callFromThread(_reapAllProcesses))
+            if getattr(signal, "siginterrupt", None) is not None:
+                signal.siginterrupt(signal.SIGCHLD, False)
+            # Like the base, reap processes now in case a process
+            # exited before the handlers above were installed.
+            _reapAllProcesses()
+
+
+class _GIWaker(posixbase._UnixWaker):
+    """
+    Run scheduled events after waking up.
+    """
+
+    def doRead(self):
+        posixbase._UnixWaker.doRead(self)
+        self.reactor._simulate()
+
+
+class GIReactor(_GISignalMixin,
+                posixbase.PosixReactorBase, posixbase._PollLikeMixin):
+    """
+    GObject event loop reactor.
+
+    @ivar _sources: A dictionary mapping L{FileDescriptor} instances to
+        GSource handles.
+
+    @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
+        reading.
+
+    @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
+        writing.
+
+    @ivar _simtag: A GSource handle for the next L{simulate} call.
+    """
+    implements(IReactorFDSet)
+
+    _POLL_DISCONNECTED = POLL_DISCONNECTED
+    _POLL_IN = GLib.IOCondition.IN
+    _POLL_OUT = GLib.IOCondition.OUT
+
+    # Install a waker that knows it needs to call C{_simulate} in order to run
+    # callbacks queued from a thread:
+    _wakerFactory = _GIWaker
+
+    def __init__(self, useGtk=False):
+        self._simtag = None
+        self._reads = set()
+        self._writes = set()
+        self._sources = {}
+        posixbase.PosixReactorBase.__init__(self)
+
+        if useGtk:
+            if 'gobject' in sys.modules:
+                import gtk as Gtk
+            else:
+                from gi.repository import Gtk
+
+            def mainquit():
+                if Gtk.main_level():
+                    Gtk.main_quit()
+
+            self.__pending = Gtk.events_pending
+            self.__iteration = Gtk.main_iteration
+            self.__crash = mainquit
+            self.__run = Gtk.main
+        else:
+            self.context = GLib.main_context_default()
+            self.__pending = self.context.pending
+            self.__iteration = self.context.iteration
+            self.loop = GLib.MainLoop()
+            self.__crash = lambda: GLib.idle_add(self.loop.quit)
+            self.__run = self.loop.run
+
+    # The input_add function in pygtk1 checks for objects with a
+    # 'fileno' method and, if present, uses the result of that method
+    # as the input source. The pygtk2 input_add does not do this. The
+    # function below replicates the pygtk1 functionality.
+
+    # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
+    # g_io_add_watch() takes different condition bitfields than
+    # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
+    # bug.
+    def input_add(self, source, condition, callback):
+        if hasattr(source, 'fileno'):
+            # handle python objects
+            def wrapper(source, condition, real_s=source, real_cb=callback):
+                return real_cb(real_s, condition)
+            return GLib.io_add_watch(source.fileno(), condition, wrapper)
+        else:
+            return GLib.io_add_watch(source, condition, callback)
+
+    def _ioEventCallback(self, source, condition):
+        """
+        Called by event loop when an I/O event occurs.
+        """
+        log.callWithLogger(
+            source, self._doReadOrWrite, source, source, condition)
+        return True  # True = don't auto-remove the source
+
+    def _add(self, source, primary, other, primaryFlag, otherFlag):
+        """
+        Add the given L{FileDescriptor} for monitoring either for reading or
+        writing. If the file is already monitored for the other operation, we
+        delete the previous registration and re-register it for both reading
+        and writing.
+        """
+        if source in primary:
+            return
+        flags = primaryFlag
+        if source in other:
+            GLib.source_remove(self._sources[source])
+            flags |= otherFlag
+        self._sources[source] = self.input_add(
+            source, flags, self._ioEventCallback)
+        primary.add(source)
+
+    def addReader(self, reader):
+        """
+        Add a L{FileDescriptor} for monitoring of data available to read.
+        """
+        self._add(reader, self._reads, self._writes, INFLAGS, OUTFLAGS)
+
+    def addWriter(self, writer):
+        """
+        Add a L{FileDescriptor} for monitoring ability to write data.
+        """
+        self._add(writer, self._writes, self._reads, OUTFLAGS, INFLAGS)
+
+    def getReaders(self):
+        """
+        Retrieve the list of current L{FileDescriptor} monitored for reading.
+        """
+        return list(self._reads)
+
+    def getWriters(self):
+        """
+        Retrieve the list of current L{FileDescriptor} monitored for writing.
+        """
+        return list(self._writes)
+
+    def removeAll(self):
+        """
+        Remove monitoring for all registered L{FileDescriptor}s.
+        """
+        return self._removeAll(self._reads, self._writes)
+
+    def _remove(self, source, primary, other, flags):
+        """
+        Remove monitoring the given L{FileDescriptor} for either reading or
+        writing. If it's still monitored for the other operation, we
+        re-register the L{FileDescriptor} for only that operation.
+        """
+        if source not in primary:
+            return
+        GLib.source_remove(self._sources[source])
+        primary.remove(source)
+        if source in other:
+            self._sources[source] = self.input_add(
+                source, flags, self._ioEventCallback)
+        else:
+            self._sources.pop(source)
+
+    def removeReader(self, reader):
+        """
+        Stop monitoring the given L{FileDescriptor} for reading.
+        """
+        self._remove(reader, self._reads, self._writes, OUTFLAGS)
+
+    def removeWriter(self, writer):
+        """
+        Stop monitoring the given L{FileDescriptor} for writing.
+        """
+        self._remove(writer, self._writes, self._reads, INFLAGS)
+
+    def iterate(self, delay=0):
+        """
+        One iteration of the event loop, for trial's use.
+
+        This is not used for actual reactor runs.
+        """
+        self.runUntilCurrent()
+        while self.__pending():
+            self.__iteration(0)
+
+    def crash(self):
+        """
+        Crash the reactor.
+        """
+        posixbase.PosixReactorBase.crash(self)
+        self.__crash()
+
+    def stop(self):
+        """
+        Stop the reactor.
+        """
+        posixbase.PosixReactorBase.stop(self)
+        # The base implementation only sets a flag, to ensure shutting down is
+        # not reentrant. Unfortunately, this flag is not meaningful to the
+        # gobject event loop. We therefore call wakeUp() to ensure the event
+        # loop will call back into Twisted once this iteration is done. This
+        # will result in self.runUntilCurrent() being called, where the stop
+        # flag will trigger the actual shutdown process, eventually calling
+        # crash() which will do the actual gobject event loop shutdown.
+        self.wakeUp()
+
+    def run(self, installSignalHandlers=True):
+        """
+        Run the reactor.
+        """
+        self.callWhenRunning(self._reschedule)
+        self.startRunning(installSignalHandlers=installSignalHandlers)
+        if self._started:
+            self.__run()
+
+    def callLater(self, *args, **kwargs):
+        """
+        Schedule a C{DelayedCall}.
+        """
+        result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs)
+        # Make sure we'll get woken up at correct time to handle this new
+        # scheduled call:
+        self._reschedule()
+        return result
+
+    def _reschedule(self):
+        """
+        Schedule a glib timeout for C{_simulate}.
+        """
+        if self._simtag is not None:
+            GLib.source_remove(self._simtag)
+            self._simtag = None
+        timeout = self.timeout()
+        if timeout is not None:
+            self._simtag = GLib.timeout_add(int(timeout * 1000),
+                                            self._simulate)
+
+    def _simulate(self):
+        """
+        Run timers, and then reschedule glib timeout for next scheduled event.
+        """
+        self.runUntilCurrent()
+        self._reschedule()
+
+
+class PortableGIReactor(_GISignalMixin, selectreactor.SelectReactor):
+    """
+    Portable GObject Introspection event loop reactor.
+    """
+    def __init__(self, useGtk=False):
+        self._simtag = None
+        selectreactor.SelectReactor.__init__(self)
+
+        if useGtk:
+            if 'gobject' in sys.modules:
+                import gtk as Gtk
+            else:
+                from gi.repository import Gtk
+
+            def mainquit():
+                if Gtk.main_level():
+                    Gtk.main_quit()
+
+            self.__crash = mainquit
+            self.__run = Gtk.main
+        else:
+            self.loop = GLib.MainLoop()
+            self.__crash = lambda: GLib.idle_add(self.loop.quit)
+            self.__run = self.loop.run
+
+    def crash(self):
+        selectreactor.SelectReactor.crash(self)
+        self.__crash()
+
+    def run(self, installSignalHandlers=True):
+        self.startRunning(installSignalHandlers=installSignalHandlers)
+        GLib.idle_add(self.simulate)
+        self.__run()
+
+    def simulate(self):
+        """
+        Run simulation loops and reschedule callbacks.
+        """
+        if self._simtag is not None:
+            GLib.source_remove(self._simtag)
+        self.iterate()
+        timeout = min(self.timeout(), 0.1)
+        if timeout is None:
+            timeout = 0.1
+        self._simtag = GLib.timeout_add(int(timeout * 1000), self.simulate)
+
+
+def install(useGtk=False):
+    """
+    Configure the twisted mainloop to be run inside the glib mainloop.
+
+    @param useGtk: should GTK+ rather than glib event loop be
+        used (this will be slightly slower but does support GUI).
+    """
+    if runtime.platform.getType() == 'posix':
+        reactor = GIReactor(useGtk=useGtk)
+    else:
+        reactor = PortableGIReactor(useGtk=useGtk)
+
+    from twisted.internet.main import installReactor
+    installReactor(reactor)
+    return reactor
+
+
+__all__ = ['install']

=== modified file 'twisted/internet/test/reactormixins.py'
--- twisted/internet/test/reactormixins.py	2011-08-28 14:09:23 +0000
+++ twisted/internet/test/reactormixins.py	2011-12-14 19:20:59 +0000
@@ -56,11 +56,13 @@ class ReactorBuilder:
         # it's not _really_ worth it to support on other platforms,
         # since no one really wants to use it on other platforms.
         _reactors.extend([
+                "twisted.internet.gireactor.PortableGIReactor",
                 "twisted.internet.gtk2reactor.PortableGtkReactor",
                 "twisted.internet.win32eventreactor.Win32Reactor",
                 "twisted.internet.iocpreactor.reactor.IOCPReactor"])
     else:
         _reactors.extend([
+                "twisted.internet.gireactor.GIReactor",
                 "twisted.internet.glib2reactor.Glib2Reactor",
                 "twisted.internet.gtk2reactor.Gtk2Reactor",
                 "twisted.internet.kqreactor.KQueueReactor"])

=== modified file 'twisted/plugins/twisted_reactors.py'
--- twisted/plugins/twisted_reactors.py	2011-05-05 01:48:02 +0000
+++ twisted/plugins/twisted_reactors.py	2011-12-14 19:20:59 +0000
@@ -11,6 +11,8 @@ select = Reactor(
     'select', 'twisted.internet.selectreactor', 'select(2)-based reactor.')
 wx = Reactor(
     'wx', 'twisted.internet.wxreactor', 'wxPython integration reactor.')
+gi = Reactor(
+    'gi', 'twisted.internet.gireactor', 'GObject Introspection integration reactor.')
 gtk = Reactor(
     'gtk', 'twisted.internet.gtkreactor', 'Gtk1 integration reactor.')
 gtk2 = Reactor(

