1# Copyright (c) Twisted Matrix Laboratories.
2# See LICENSE for details.
3
4"""
5This module provides support for Twisted to interact with the glib
6mainloop via GObject Introspection.
7
8In order to use this support, simply do the following::
9
10    from twisted.internet import gireactor
11    gireactor.install()
12
13If you wish to use a GApplication, register it with the reactor::
14
15    from twisted.internet import reactor
16    reactor.registerGApplication(app)
17
18Then use twisted.internet APIs as usual.
19
20On Python 3, pygobject v3.4 or later is required.
21"""
22
23from __future__ import division, absolute_import
24
25from twisted.python.compat import _PY3
26from twisted.internet.error import ReactorAlreadyRunning
27from twisted.internet import _glibbase
28from twisted.python import runtime
29
30if _PY3:
31    # We require a sufficiently new version of pygobject, so always exists:
32    _pygtkcompatPresent = True
33else:
34    # We can't just try to import gi.pygtkcompat, because that would import
35    # gi, and the goal here is to not import gi in cases where that would
36    # cause segfault.
37    from twisted.python.modules import theSystemPath
38    _pygtkcompatPresent = True
39    try:
40        theSystemPath["gi.pygtkcompat"]
41    except KeyError:
42        _pygtkcompatPresent = False
43
44
45# Modules that we want to ensure aren't imported if we're on older versions of
46# GI:
47_PYGTK_MODULES = ['gobject', 'glib', 'gio', 'gtk']
48
49def _oldGiInit():
50    """
51    Make sure pygtk and gi aren't loaded at the same time, and import Glib if
52    possible.
53    """
54    # We can't immediately prevent imports, because that confuses some buggy
55    # code in gi:
56    _glibbase.ensureNotImported(
57        _PYGTK_MODULES,
58        "Introspected and static glib/gtk bindings must not be mixed; can't "
59        "import gireactor since pygtk2 module is already imported.")
60
61    global GLib
62    from gi.repository import GLib
63    if getattr(GLib, "threads_init", None) is not None:
64        GLib.threads_init()
65
66    _glibbase.ensureNotImported([], "",
67                                preventImports=_PYGTK_MODULES)
68
69
70if not _pygtkcompatPresent:
71    # Older versions of gi don't have compatability layer, so just enforce no
72    # imports of pygtk and gi at same time:
73    _oldGiInit()
74else:
75    # Newer version of gi, so we can try to initialize compatibility layer; if
76    # real pygtk was already imported we'll get ImportError at this point
77    # rather than segfault, so unconditional import is fine.
78    import gi.pygtkcompat
79    gi.pygtkcompat.enable()
80    # At this point importing gobject will get you gi version, and importing
81    # e.g. gtk will either fail in non-segfaulty way or use gi version if user
82    # does gi.pygtkcompat.enable_gtk(). So, no need to prevent imports of
83    # old school pygtk modules.
84    from gi.repository import GLib
85    if getattr(GLib, "threads_init", None) is not None:
86        GLib.threads_init()
87
88
89
90class GIReactor(_glibbase.GlibReactorBase):
91    """
92    GObject-introspection event loop reactor.
93
94    @ivar _gapplication: A C{Gio.Application} instance that was registered
95        with C{registerGApplication}.
96    """
97    _POLL_DISCONNECTED = (GLib.IOCondition.HUP | GLib.IOCondition.ERR |
98                          GLib.IOCondition.NVAL)
99    _POLL_IN = GLib.IOCondition.IN
100    _POLL_OUT = GLib.IOCondition.OUT
101
102    # glib's iochannel sources won't tell us about any events that we haven't
103    # asked for, even if those events aren't sensible inputs to the poll()
104    # call.
105    INFLAGS = _POLL_IN | _POLL_DISCONNECTED
106    OUTFLAGS = _POLL_OUT | _POLL_DISCONNECTED
107
108    # By default no Application is registered:
109    _gapplication = None
110
111
112    def __init__(self, useGtk=False):
113        _gtk = None
114        if useGtk is True:
115            from gi.repository import Gtk as _gtk
116
117        _glibbase.GlibReactorBase.__init__(self, GLib, _gtk, useGtk=useGtk)
118
119
120    def registerGApplication(self, app):
121        """
122        Register a C{Gio.Application} or C{Gtk.Application}, whose main loop
123        will be used instead of the default one.
124
125        We will C{hold} the application so it doesn't exit on its own. In
126        versions of C{python-gi} 3.2 and later, we exit the event loop using
127        the C{app.quit} method which overrides any holds. Older versions are
128        not supported.
129        """
130        if self._gapplication is not None:
131            raise RuntimeError(
132                "Can't register more than one application instance.")
133        if self._started:
134            raise ReactorAlreadyRunning(
135                "Can't register application after reactor was started.")
136        if not hasattr(app, "quit"):
137            raise RuntimeError("Application registration is not supported in"
138                               " versions of PyGObject prior to 3.2.")
139        self._gapplication = app
140        def run():
141            app.hold()
142            app.run(None)
143        self._run = run
144
145        self._crash = app.quit
146
147
148
149class PortableGIReactor(_glibbase.PortableGlibReactorBase):
150    """
151    Portable GObject Introspection event loop reactor.
152    """
153    def __init__(self, useGtk=False):
154        _gtk = None
155        if useGtk is True:
156            from gi.repository import Gtk as _gtk
157
158        _glibbase.PortableGlibReactorBase.__init__(self, GLib, _gtk,
159                                                   useGtk=useGtk)
160
161
162    def registerGApplication(self, app):
163        """
164        Register a C{Gio.Application} or C{Gtk.Application}, whose main loop
165        will be used instead of the default one.
166        """
167        raise NotImplementedError("GApplication is not currently supported on Windows.")
168
169
170
171def install(useGtk=False):
172    """
173    Configure the twisted mainloop to be run inside the glib mainloop.
174
175    @param useGtk: should GTK+ rather than glib event loop be
176        used (this will be slightly slower but does support GUI).
177    """
178    if runtime.platform.getType() == 'posix':
179        reactor = GIReactor(useGtk=useGtk)
180    else:
181        reactor = PortableGIReactor(useGtk=useGtk)
182
183    from twisted.internet.main import installReactor
184    installReactor(reactor)
185    return reactor
186
187
188__all__ = ['install']
189