1 # -*- test-case-name: twisted.internet.test -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 This module provides base support for Twisted to interact with the glib/gtk
9 The classes in this module should not be used directly, but rather you should
10 import gireactor or gtk3reactor for GObject Introspection based applications,
11 or glib2reactor or gtk2reactor for applications using legacy static bindings.
16 from twisted.internet import base, posixbase, selectreactor
17 from twisted.internet.interfaces import IReactorFDSet
18 from twisted.python import log
19 from twisted.python.compat import set
20 from zope.interface import implements
23 def ensureNotImported(moduleNames, errorMessage, preventImports=[]):
25 Check whether the given modules were imported, and if requested, ensure
26 they will not be importable in the future.
28 @param moduleNames: A list of module names we make sure aren't imported.
29 @type moduleNames: C{list} of C{str}
31 @param preventImports: A list of module name whose future imports should
33 @type preventImports: C{list} of C{str}
35 @param errorMessage: Message to use when raising an C{ImportError}.
36 @type errorMessage: C{str}
38 @raises: C{ImportError} with given error message if a given module name
39 has already been imported.
41 for name in moduleNames:
42 if sys.modules.get(name) is not None:
43 raise ImportError(errorMessage)
45 # Disable module imports to avoid potential problems.
46 for name in preventImports:
47 sys.modules[name] = None
51 class GlibWaker(posixbase._UnixWaker):
53 Run scheduled events after waking up.
57 posixbase._UnixWaker.doRead(self)
58 self.reactor._simulate()
62 class GlibReactorBase(posixbase.PosixReactorBase, posixbase._PollLikeMixin):
64 Base class for GObject event loop reactors.
66 Notification for I/O events (reads and writes on file descriptors) is done
67 by the the gobject-based event loop. File descriptors are registered with
68 gobject with the appropriate flags for read/write/disconnect notification.
70 Time-based events, the results of C{callLater} and C{callFromThread}, are
71 handled differently. Rather than registering each event with gobject, a
72 single gobject timeout is registered for the earliest scheduled event, the
73 output of C{reactor.timeout()}. For example, if there are timeouts in 1, 2
74 and 3.4 seconds, a single timeout is registered for 1 second in the
75 future. When this timeout is hit, C{_simulate} is called, which calls the
76 appropriate Twisted-level handlers, and a new timeout is added to gobject
77 by the C{_reschedule} method.
79 To handle C{callFromThread} events, we use a custom waker that calls
80 C{_simulate} whenever it wakes up.
82 @ivar _sources: A dictionary mapping L{FileDescriptor} instances to
85 @ivar _reads: A set of L{FileDescriptor} instances currently monitored for
88 @ivar _writes: A set of L{FileDescriptor} instances currently monitored for
91 @ivar _simtag: A GSource handle for the next L{simulate} call.
93 implements(IReactorFDSet)
95 # Install a waker that knows it needs to call C{_simulate} in order to run
96 # callbacks queued from a thread:
97 _wakerFactory = GlibWaker
99 def __init__(self, glib_module, gtk_module, useGtk=False):
104 self._glib = glib_module
105 self._gtk = gtk_module
106 posixbase.PosixReactorBase.__init__(self)
108 self._source_remove = self._glib.source_remove
109 self._timeout_add = self._glib.timeout_add
112 if self._gtk.main_level():
113 self._gtk.main_quit()
116 self._pending = self._gtk.events_pending
117 self._iteration = self._gtk.main_iteration_do
118 self._crash = _mainquit
119 self._run = self._gtk.main
121 self.context = self._glib.main_context_default()
122 self._pending = self.context.pending
123 self._iteration = self.context.iteration
124 self.loop = self._glib.MainLoop()
125 self._crash = lambda: self._glib.idle_add(self.loop.quit)
126 self._run = self.loop.run
129 def _handleSignals(self):
130 # First, install SIGINT and friends:
131 base._SignalReactorMixin._handleSignals(self)
132 # Next, since certain versions of gtk will clobber our signal handler,
133 # set all signal handlers again after the event loop has started to
134 # ensure they're *really* set. We don't call this twice so we don't
135 # leak file descriptors created in the SIGCHLD initialization:
136 self.callLater(0, posixbase.PosixReactorBase._handleSignals, self)
139 # The input_add function in pygtk1 checks for objects with a
140 # 'fileno' method and, if present, uses the result of that method
141 # as the input source. The pygtk2 input_add does not do this. The
142 # function below replicates the pygtk1 functionality.
144 # In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
145 # g_io_add_watch() takes different condition bitfields than
146 # gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
148 def input_add(self, source, condition, callback):
149 if hasattr(source, 'fileno'):
150 # handle python objects
151 def wrapper(ignored, condition):
152 return callback(source, condition)
153 fileno = source.fileno()
157 return self._glib.io_add_watch(
158 fileno, condition, wrapper,
159 priority=self._glib.PRIORITY_DEFAULT_IDLE)
162 def _ioEventCallback(self, source, condition):
164 Called by event loop when an I/O event occurs.
167 source, self._doReadOrWrite, source, source, condition)
168 return True # True = don't auto-remove the source
171 def _add(self, source, primary, other, primaryFlag, otherFlag):
173 Add the given L{FileDescriptor} for monitoring either for reading or
174 writing. If the file is already monitored for the other operation, we
175 delete the previous registration and re-register it for both reading
178 if source in primary:
182 self._source_remove(self._sources[source])
184 self._sources[source] = self.input_add(
185 source, flags, self._ioEventCallback)
189 def addReader(self, reader):
191 Add a L{FileDescriptor} for monitoring of data available to read.
193 self._add(reader, self._reads, self._writes,
194 self.INFLAGS, self.OUTFLAGS)
197 def addWriter(self, writer):
199 Add a L{FileDescriptor} for monitoring ability to write data.
201 self._add(writer, self._writes, self._reads,
202 self.OUTFLAGS, self.INFLAGS)
205 def getReaders(self):
207 Retrieve the list of current L{FileDescriptor} monitored for reading.
209 return list(self._reads)
212 def getWriters(self):
214 Retrieve the list of current L{FileDescriptor} monitored for writing.
216 return list(self._writes)
221 Remove monitoring for all registered L{FileDescriptor}s.
223 return self._removeAll(self._reads, self._writes)
226 def _remove(self, source, primary, other, flags):
228 Remove monitoring the given L{FileDescriptor} for either reading or
229 writing. If it's still monitored for the other operation, we
230 re-register the L{FileDescriptor} for only that operation.
232 if source not in primary:
234 self._source_remove(self._sources[source])
235 primary.remove(source)
237 self._sources[source] = self.input_add(
238 source, flags, self._ioEventCallback)
240 self._sources.pop(source)
243 def removeReader(self, reader):
245 Stop monitoring the given L{FileDescriptor} for reading.
247 self._remove(reader, self._reads, self._writes, self.OUTFLAGS)
250 def removeWriter(self, writer):
252 Stop monitoring the given L{FileDescriptor} for writing.
254 self._remove(writer, self._writes, self._reads, self.INFLAGS)
257 def iterate(self, delay=0):
259 One iteration of the event loop, for trial's use.
261 This is not used for actual reactor runs.
263 self.runUntilCurrent()
264 while self._pending():
272 posixbase.PosixReactorBase.crash(self)
280 posixbase.PosixReactorBase.stop(self)
281 # The base implementation only sets a flag, to ensure shutting down is
282 # not reentrant. Unfortunately, this flag is not meaningful to the
283 # gobject event loop. We therefore call wakeUp() to ensure the event
284 # loop will call back into Twisted once this iteration is done. This
285 # will result in self.runUntilCurrent() being called, where the stop
286 # flag will trigger the actual shutdown process, eventually calling
287 # crash() which will do the actual gobject event loop shutdown.
291 def run(self, installSignalHandlers=True):
295 self.callWhenRunning(self._reschedule)
296 self.startRunning(installSignalHandlers=installSignalHandlers)
301 def callLater(self, *args, **kwargs):
303 Schedule a C{DelayedCall}.
305 result = posixbase.PosixReactorBase.callLater(self, *args, **kwargs)
306 # Make sure we'll get woken up at correct time to handle this new
312 def _reschedule(self):
314 Schedule a glib timeout for C{_simulate}.
316 if self._simtag is not None:
317 self._source_remove(self._simtag)
319 timeout = self.timeout()
320 if timeout is not None:
321 self._simtag = self._timeout_add(
322 int(timeout * 1000), self._simulate,
323 priority=self._glib.PRIORITY_DEFAULT_IDLE)
328 Run timers, and then reschedule glib timeout for next scheduled event.
330 self.runUntilCurrent()
335 class PortableGlibReactorBase(selectreactor.SelectReactor):
337 Base class for GObject event loop reactors that works on Windows.
339 Sockets aren't supported by GObject's input_add on Win32.
341 def __init__(self, glib_module, gtk_module, useGtk=False):
343 self._glib = glib_module
344 self._gtk = gtk_module
345 selectreactor.SelectReactor.__init__(self)
347 self._source_remove = self._glib.source_remove
348 self._timeout_add = self._glib.timeout_add
351 if self._gtk.main_level():
352 self._gtk.main_quit()
355 self._crash = _mainquit
356 self._run = self._gtk.main
358 self.loop = self._glib.MainLoop()
359 self._crash = lambda: self._glib.idle_add(self.loop.quit)
360 self._run = self.loop.run
364 selectreactor.SelectReactor.crash(self)
368 def run(self, installSignalHandlers=True):
369 self.startRunning(installSignalHandlers=installSignalHandlers)
370 self._timeout_add(0, self.simulate)
377 Run simulation loops and reschedule callbacks.
379 if self._simtag is not None:
380 self._source_remove(self._simtag)
382 timeout = min(self.timeout(), 0.01)
385 self._simtag = self._timeout_add(
386 int(timeout * 1000), self.simulate,
387 priority=self._glib.PRIORITY_DEFAULT_IDLE)