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