=== added file 'twisted/internet/_glibbase.py'
--- twisted/internet/_glibbase.py	1970-01-01 00:00:00 +0000
+++ twisted/internet/_glibbase.py	2012-01-09 20:43:47 +0000
@@ -0,0 +1,271 @@
+# -*- test-case-name: twisted.internet.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# Copyright (c) 2011-2012 Canonical Ltd.
+# See LICENSE for details.
+"""
+This module provides base support for Twisted to interact with the glib/gtk
+mainloops.
+
+The classes in this module should not be used directly, but rather you should
+import gireactor or gtk3reactor for GObject Introspection based applications,
+or glib2reactor or gtk2reactor for applications using legacy static bindings.
+"""
+
+import signal
+
+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
+
+class GlibSignalMixin(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 GlibWaker(posixbase._UnixWaker):
+    """
+    Run scheduled events after waking up.
+    """
+
+    def doRead(self):
+        posixbase._UnixWaker.doRead(self)
+        self.reactor._simulate()
+
+
+class GlibReactorBase(GlibSignalMixin,
+                      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)
+
+    # Install a waker that knows it needs to call C{_simulate} in order to run
+    # callbacks queued from a thread:
+    _wakerFactory = GlibWaker
+
+    def __init__(self):
+        self._simtag = None
+        self._reads = set()
+        self._writes = set()
+        self._sources = {}
+        posixbase.PosixReactorBase.__init__(self)
+
+    def input_add(self, source, condition, callback):
+        """This is a stub to be implemented by inheriting classes."""
+        raise NotImplementedError()
+
+    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:
+            self._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,
+                  self.INFLAGS, self.OUTFLAGS)
+
+    def addWriter(self, writer):
+        """
+        Add a L{FileDescriptor} for monitoring ability to write data.
+        """
+        self._add(writer, self._writes, self._reads,
+                  self.OUTFLAGS, self.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
+        self._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, self.OUTFLAGS)
+
+    def removeWriter(self, writer):
+        """
+        Stop monitoring the given L{FileDescriptor} for writing.
+        """
+        self._remove(writer, self._writes, self._reads, self.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:
+            self._source_remove(self._simtag)
+            self._simtag = None
+        timeout = self.timeout()
+        if timeout is not None:
+            self._simtag = self._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 PortableGlibReactorBase(GlibSignalMixin, selectreactor.SelectReactor):
+    """
+    Portable GObject event loop reactor.
+    """
+    def __init__(self):
+        self._simtag = None
+        selectreactor.SelectReactor.__init__(self)
+
+    def crash(self):
+        selectreactor.SelectReactor.crash(self)
+        self._crash()
+
+    def run(self, installSignalHandlers=True):
+        self.startRunning(installSignalHandlers=installSignalHandlers)
+        self.idle_add(self.simulate)
+        self._run()
+
+    def simulate(self):
+        """
+        Run simulation loops and reschedule callbacks.
+        """
+        if self._simtag is not None:
+            self._source_remove(self._simtag)
+        self.iterate()
+        timeout = min(self.timeout(), 0.1)
+        if timeout is None:
+            timeout = 0.1
+        self._simtag = self._timeout_add(int(timeout * 1000), self.simulate)

=== added file 'twisted/internet/gireactor.py'
--- twisted/internet/gireactor.py	1970-01-01 00:00:00 +0000
+++ twisted/internet/gireactor.py	2012-01-10 20:54:07 +0000
@@ -0,0 +1,149 @@
+# -*- test-case-name: twisted.internet.test -*-
+# Copyright (c) Twisted Matrix Laboratories.
+# Copyright (c) 2011-2012 Canonical Ltd.
+# See LICENSE for details.
+
+"""
+This module provides support for Twisted to interact with the glib/gtk3
+mainloop via GObject Introspection.
+
+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 sys
+
+if 'gobject' in sys.modules:
+    raise ImportError(('Introspected and static bindings must not be mixed.'
+                       ' Use glib2reactor or gtk2reactor instead.'))
+
+
+from gi.repository import GLib
+# We need to override sys.modules with these to prevent imports.
+# This is required, as importing these can result in SEGFAULTs.
+sys.modules['glib'] = None
+sys.modules['gobject'] = None
+sys.modules['gio'] = None
+sys.modules['gtk'] = None
+
+from twisted.internet import _glibbase
+from twisted.python import runtime
+
+GLib.threads_init()
+
+
+class GIReactor(_glibbase.GlibReactorBase):
+    """
+    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.
+    """
+    _POLL_DISCONNECTED = (GLib.IOCondition.HUP | GLib.IOCondition.ERR |
+                          GLib.IOCondition.NVAL)
+    _POLL_IN = GLib.IOCondition.IN
+    _POLL_OUT = GLib.IOCondition.OUT
+
+    # 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 = _POLL_IN | _POLL_DISCONNECTED
+    OUTFLAGS = _POLL_OUT | _POLL_DISCONNECTED
+
+    def __init__(self, useGtk=False):
+        _glibbase.GlibReactorBase.__init__(self)
+
+        self._source_remove = GLib.source_remove
+        self._timeout_add = GLib.timeout_add
+
+        if useGtk:
+            from gi.repository import Gtk
+
+            self._pending = Gtk.events_pending
+            self._iteration = Gtk.main_iteration_do
+            self._crash = Gtk.main_quit
+            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)
+
+
+class PortableGIReactor(_glibbase.PortableGlibReactorBase):
+    """
+    Portable GObject Introspection event loop reactor.
+    """
+    def __init__(self, useGtk=False):
+        _glibbase.PortableGlibReactorBase.__init__(self)
+
+        self._source_remove = GLib.source_remove
+        self._idle_add = GLib.idle_add
+        self._timeout_add = GLib.timeout_add
+
+        if useGtk:
+            from gi.repository import Gtk
+
+            self._crash = Gtk.main_quit
+            self._run = Gtk.main
+        else:
+            self.loop = GLib.MainLoop()
+            self._crash = lambda: GLib.idle_add(self.loop.quit)
+            self._run = self.loop.run
+
+
+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/gtk2reactor.py'
--- twisted/internet/gtk2reactor.py	2011-12-08 19:02:05 +0000
+++ twisted/internet/gtk2reactor.py	2012-01-10 18:38:59 +0000
@@ -21,9 +21,14 @@ integration.
 """
 
 # System Imports
-import sys, signal
+import sys
 
-from zope.interface import implements
+if 'gi' in sys.modules:
+    raise ImportError(('Introspected and static bindings must not be mixed.'
+                       ' Use twisted.internet.gireactor instead.'))
+
+# Disable gi imports to avoid potential problems.
+sys.modules['gi'] = None
 
 try:
     if not hasattr(sys, 'frozen'):
@@ -40,66 +45,11 @@ if hasattr(gobject, "threads_init"):
     gobject.threads_init()
 
 # Twisted Imports
-from twisted.python import log, runtime
-from twisted.python.compat import set
-from twisted.internet.interfaces import IReactorFDSet
-from twisted.internet import base, posixbase, selectreactor
-
-POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_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 = gobject.IO_IN | POLL_DISCONNECTED
-OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
-
-
-
-def _our_mainquit():
-    # XXX: gtk.main_quit() (which is used for crash()) raises an exception if
-    # gtk.main_level() == 0; however, all the tests freeze if we use this
-    # function to stop the reactor.  what gives?  (I believe this may have been
-    # a stupid mistake where I forgot to import gtk here... I will remove this
-    # comment if the tests pass)
-    import gtk
-    if gtk.main_level():
-        gtk.main_quit()
-
-
-
-class _Gtk2SignalMixin(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()
-
+from twisted.internet import _glibbase
+from twisted.python import runtime
 
 
-class _Gtk2Waker(posixbase._UnixWaker):
-    """
-    Run scheduled events after waking up.
-    """
-
-    def doRead(self):
-        posixbase._UnixWaker.doRead(self)
-        self.reactor._simulate()
-
-
-
-class Gtk2Reactor(_Gtk2SignalMixin, posixbase.PosixReactorBase, posixbase._PollLikeMixin):
+class Gtk2Reactor(_glibbase.GlibReactorBase):
     """
     GTK+-2 event loop reactor.
 
@@ -130,37 +80,42 @@ class Gtk2Reactor(_Gtk2SignalMixin, posi
 
     @ivar _simtag: A gtk timeout handle for the next L{_simulate} call.
     """
-    implements(IReactorFDSet)
-
-    _POLL_DISCONNECTED = POLL_DISCONNECTED
+    _POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
     _POLL_IN = gobject.IO_IN
     _POLL_OUT = gobject.IO_OUT
 
-    # Install a waker that knows it needs to call C{_simulate} in order to run
-    # callbacks queued from a thread:
-    _wakerFactory = _Gtk2Waker
+    # 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 = _POLL_IN | _POLL_DISCONNECTED
+    OUTFLAGS = _POLL_OUT | _POLL_DISCONNECTED
 
     def __init__(self, useGtk=True):
-        self._simtag = None
-        self._reads = set()
-        self._writes = set()
-        self._sources = {}
-        posixbase.PosixReactorBase.__init__(self)
+        _glibbase.GlibReactorBase.__init__(self)
+
+        self._source_remove = gobject.source_remove
+        self._timeout_add = gobject.timeout_add
+
         # pre 2.3.91 the glib iteration and mainloop functions didn't release
         # global interpreter lock, thus breaking thread and signal support.
         if getattr(gobject, "pygtk_version", ()) >= (2, 3, 91) and not useGtk:
             self.context = gobject.main_context_default()
-            self.__pending = self.context.pending
-            self.__iteration = self.context.iteration
+            self._pending = self.context.pending
+            self._iteration = self.context.iteration
             self.loop = gobject.MainLoop()
-            self.__crash = self.loop.quit
-            self.__run = self.loop.run
+            self._crash = self.loop.quit
+            self._run = self.loop.run
         else:
             import gtk
-            self.__pending = gtk.events_pending
-            self.__iteration = gtk.main_iteration
-            self.__crash = _our_mainquit
-            self.__run = gtk.main
+
+            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
 
 
     # The input_add function in pygtk1 checks for objects with a
@@ -182,220 +137,32 @@ class Gtk2Reactor(_Gtk2SignalMixin, posi
             return gobject.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 1 # 1=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:
-            gobject.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
-        gobject.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=1):
-        """
-        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:
-            gobject.source_remove(self._simtag)
-            self._simtag = None
-        timeout = self.timeout()
-        if timeout is not None:
-            self._simtag = gobject.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 PortableGtkReactor(_Gtk2SignalMixin, selectreactor.SelectReactor):
+class PortableGtkReactor(_glibbase.PortableGlibReactorBase):
     """
     Reactor that works on Windows.
 
     Sockets aren't supported by GTK+'s input_add on Win32.
     """
-    _simtag = None
+    def __init__(self, useGtk=False):
+        _glibbase.PortableGlibReactorBase.__init__(self)
 
-    def crash(self):
-        selectreactor.SelectReactor.crash(self)
-        import gtk
-        # mainquit is deprecated in newer versions
-        if gtk.main_level():
-            if hasattr(gtk, 'main_quit'):
-                gtk.main_quit()
-            else:
-                gtk.mainquit()
-
-
-    def run(self, installSignalHandlers=1):
-        import gtk
-        self.startRunning(installSignalHandlers=installSignalHandlers)
-        gobject.timeout_add(0, self.simulate)
-        # mainloop is deprecated in newer versions
-        if self._started:
-            if hasattr(gtk, 'main'):
-                gtk.main()
-            else:
-                gtk.mainloop()
-
-
-    def simulate(self):
-        """
-        Run simulation loops and reschedule callbacks.
-        """
-        if self._simtag is not None:
-            gobject.source_remove(self._simtag)
-        self.iterate()
-        timeout = min(self.timeout(), 0.01)
-        if timeout is None:
-            timeout = 0.01
-        # grumble
-        self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
+        self._source_remove = gobject.source_remove
+        self._idle_add = gobject.idle_add
+        self._timeout_add = gobject.timeout_add
 
+        if useGtk:
+            import gtk
+
+            def mainquit():
+                if gtk.main_level():
+                    gtk.main_quit()
+
+            self._crash = mainquit
+            self._run = gtk.main
+        else:
+            self.loop = gobject.MainLoop()
+            self._crash = lambda: gobject.idle_add(self.loop.quit)
+            self._run = self.loop.run
 
 
 def install(useGtk=True):
@@ -411,7 +178,6 @@ def install(useGtk=True):
     return reactor
 
 
-
 def portableInstall(useGtk=True):
     """
     Configure the twisted mainloop to be run inside the gtk mainloop.
@@ -422,10 +188,8 @@ def portableInstall(useGtk=True):
     return reactor
 
 
-
 if runtime.platform.getType() != 'posix':
     install = portableInstall
 
 
-
 __all__ = ['install']

=== added file 'twisted/internet/gtk3reactor.py'
--- twisted/internet/gtk3reactor.py	1970-01-01 00:00:00 +0000
+++ twisted/internet/gtk3reactor.py	2012-01-10 22:10:01 +0000
@@ -0,0 +1,65 @@
+
+"""
+This module provides support for Twisted to interact with the gtk3 mainloop.
+This is like gi, but slightly slower and requires a working $DISPLAY.
+
+In order to use this support, simply do the following::
+
+    |  from twisted.internet import gtk3reactor
+    |  gtk3reactor.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.
+
+Maintainer: Itamar Shtull-Trauring
+"""
+
+from twisted.internet import gireactor
+from twisted.python import runtime
+
+
+class Gtk3Reactor(gireactor.GIReactor):
+    """
+    The reactor using the gtk3 mainloop.
+    """
+
+    def __init__(self, useGtk=True):
+        """
+        Override init to set the C{useGtk} flag.
+        """
+        gireactor.GIReactor.__init__(self, useGtk=useGtk)
+
+
+class PortableGtk3Reactor(gireactor.PortableGIReactor):
+    """
+    Portable GTK+ 3.x reactor.
+    """
+    def __init__(self, useGtk=True):
+        """
+        Override init to set the C{useGtk} flag.
+        """
+        gireactor.PortableGIReactor.__init__(self, useGtk=useGtk)
+
+
+def install(useGtk=True):
+    """
+    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 = Gtk3Reactor(useGtk=useGtk)
+    else:
+        reactor = PortableGtk3Reactor(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	2012-01-10 19:13:56 +0000
@@ -56,11 +56,15 @@ 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.gtk3reactor.PortableGtk3Reactor",
                 "twisted.internet.gtk2reactor.PortableGtkReactor",
                 "twisted.internet.win32eventreactor.Win32Reactor",
                 "twisted.internet.iocpreactor.reactor.IOCPReactor"])
     else:
         _reactors.extend([
+                "twisted.internet.gireactor.GIReactor",
+                "twisted.internet.gtk3reactor.Gtk3Reactor",
                 "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	2012-01-10 19:08:05 +0000
@@ -11,6 +11,10 @@ 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.')
+gtk3 = Reactor(
+    'gtk3', 'twisted.internet.gtk3reactor', 'Gtk3 integration reactor.')
 gtk = Reactor(
     'gtk', 'twisted.internet.gtkreactor', 'Gtk1 integration reactor.')
 gtk2 = Reactor(

