1
# -*- test-case-name: twisted.internet.test.test_gtk2reactor -*-
2
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
This module provides support for Twisted to interact with the glib/gtk2
10
In order to use this support, simply do the following::
12
| from twisted.internet import gtk2reactor
13
| gtk2reactor.install()
15
Then use twisted.internet APIs as usual. The other methods here are not
16
intended to be called directly.
18
When installing the reactor, you can choose whether to use the glib
19
event loop or the GTK+ event loop which is based on it but adds GUI
25
from zope.interface import implements
27
if not hasattr(sys, 'frozen'):
28
# Don't want to check this for py2exe
31
except (ImportError, AttributeError):
32
pass # maybe we're using pygtk before this hack existed.
34
if hasattr(gobject, "threads_init"):
35
# recent versions of python-gtk expose this. python-gtk=2.4.1
36
# (wrapping glib-2.4.7) does. python-gtk=2.0.0 (wrapping
37
# glib-2.2.3) does not.
38
gobject.threads_init()
41
from twisted.python import log, runtime, failure
42
from twisted.python.compat import set
43
from twisted.internet.interfaces import IReactorFDSet
44
from twisted.internet import main, posixbase, error, selectreactor
46
POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
48
# glib's iochannel sources won't tell us about any events that we haven't
49
# asked for, even if those events aren't sensible inputs to the poll()
51
INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
52
OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
57
# XXX: gtk.main_quit() (which is used for crash()) raises an exception if
58
# gtk.main_level() == 0; however, all the tests freeze if we use this
59
# function to stop the reactor. what gives? (I believe this may have been
60
# a stupid mistake where I forgot to import gtk here... I will remove this
61
# comment if the tests pass)
68
class Gtk2Reactor(posixbase.PosixReactorBase):
70
GTK+-2 event loop reactor.
72
@ivar _sources: A dictionary mapping L{FileDescriptor} instances to gtk
75
@ivar _reads: A set of L{FileDescriptor} instances currently monitored for
78
@ivar _writes: A set of L{FileDescriptor} instances currently monitored for
81
@ivar _simtag: A gtk timeout handle for the next L{simulate} call.
83
implements(IReactorFDSet)
85
def __init__(self, useGtk=True):
90
posixbase.PosixReactorBase.__init__(self)
91
# pre 2.3.91 the glib iteration and mainloop functions didn't release
92
# global interpreter lock, thus breaking thread and signal support.
93
if getattr(gobject, "pygtk_version", ()) >= (2, 3, 91) and not useGtk:
94
self.context = gobject.main_context_default()
95
self.__pending = self.context.pending
96
self.__iteration = self.context.iteration
97
self.loop = gobject.MainLoop()
98
self.__crash = self.loop.quit
99
self.__run = self.loop.run
102
self.__pending = gtk.events_pending
103
self.__iteration = gtk.main_iteration
104
self.__crash = _our_mainquit
105
self.__run = gtk.main
107
# The input_add function in pygtk1 checks for objects with a
108
# 'fileno' method and, if present, uses the result of that method
109
# as the input source. The pygtk2 input_add does not do this. The
110
# function below replicates the pygtk1 functionality.
112
# In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
113
# g_io_add_watch() takes different condition bitfields than
114
# gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
116
def input_add(self, source, condition, callback):
117
if hasattr(source, 'fileno'):
118
# handle python objects
119
def wrapper(source, condition, real_s=source, real_cb=callback):
120
return real_cb(real_s, condition)
121
return gobject.io_add_watch(source.fileno(), condition, wrapper)
123
return gobject.io_add_watch(source, condition, callback)
126
def _add(self, source, primary, other, primaryFlag, otherFlag):
128
Add the given L{FileDescriptor} for monitoring either for reading or
129
writing. If the file is already monitored for the other operation, we
130
delete the previous registration and re-register it for both reading
133
if source in primary:
137
gobject.source_remove(self._sources[source])
139
self._sources[source] = self.input_add(source, flags, self.callback)
143
def addReader(self, reader):
145
Add a L{FileDescriptor} for monitoring of data available to read.
147
self._add(reader, self._reads, self._writes, INFLAGS, OUTFLAGS)
150
def addWriter(self, writer):
152
Add a L{FileDescriptor} for monitoring ability to write data.
154
self._add(writer, self._writes, self._reads, OUTFLAGS, INFLAGS)
157
def getReaders(self):
159
Retrieve the list of current L{FileDescriptor} monitored for reading.
161
return list(self._reads)
164
def getWriters(self):
166
Retrieve the list of current L{FileDescriptor} monitored for writing.
168
return list(self._writes)
173
Remove monitoring for all registered L{FileDescriptor}s.
175
return self._removeAll(self._reads, self._writes)
178
def _remove(self, source, primary, other, flags):
180
Remove monitoring the given L{FileDescriptor} for either reading or
181
writing. If it's still monitored for the other operation, we
182
re-register the L{FileDescriptor} for only that operation.
184
if source not in primary:
186
gobject.source_remove(self._sources[source])
187
primary.remove(source)
189
self._sources[source] = self.input_add(
190
source, flags, self.callback)
192
self._sources.pop(source)
195
def removeReader(self, reader):
197
Stop monitoring the given L{FileDescriptor} for reading.
199
self._remove(reader, self._reads, self._writes, OUTFLAGS)
202
def removeWriter(self, writer):
204
Stop monitoring the given L{FileDescriptor} for writing.
206
self._remove(writer, self._writes, self._reads, INFLAGS)
209
doIterationTimer = None
211
def doIterationTimeout(self, *args):
212
self.doIterationTimer = None
213
return 0 # auto-remove
216
def doIteration(self, delay):
217
# flush some pending events, return if there was something to do
218
# don't use the usual "while self.context.pending(): self.context.iteration()"
219
# idiom because lots of IO (in particular test_tcp's
220
# ProperlyCloseFilesTestCase) can keep us from ever exiting.
221
log.msg(channel='system', event='iteration', reactor=self)
225
# nothing to do, must delay
227
return # shouldn't delay, so just return
228
self.doIterationTimer = gobject.timeout_add(int(delay * 1000),
229
self.doIterationTimeout)
230
# This will either wake up from IO or from a timeout.
231
self.__iteration(1) # block
232
# note: with the .simulate timer below, delays > 0.1 will always be
233
# woken up by the .simulate timer
234
if self.doIterationTimer:
235
# if woken by IO, need to cancel the timer
236
gobject.source_remove(self.doIterationTimer)
237
self.doIterationTimer = None
241
posixbase.PosixReactorBase.crash(self)
245
def run(self, installSignalHandlers=1):
246
self.startRunning(installSignalHandlers=installSignalHandlers)
247
gobject.timeout_add(0, self.simulate)
252
def _doReadOrWrite(self, source, condition, faildict={
253
error.ConnectionDone: failure.Failure(error.ConnectionDone()),
254
error.ConnectionLost: failure.Failure(error.ConnectionLost()),
258
if condition & POLL_DISCONNECTED and \
259
not (condition & gobject.IO_IN):
260
why = main.CONNECTION_LOST
263
if condition & gobject.IO_IN:
264
why = source.doRead()
265
didRead = source.doRead
266
if not why and condition & gobject.IO_OUT:
267
# if doRead caused connectionLost, don't call doWrite
268
# if doRead is doWrite, don't call it again.
269
if not source.disconnected and source.doWrite != didRead:
270
why = source.doWrite()
271
didRead = source.doWrite # if failed it was in write
273
why = sys.exc_info()[1]
274
log.msg('Error In %s' % source)
278
self._disconnectSelectable(source, why, didRead == source.doRead)
281
def callback(self, source, condition):
282
log.callWithLogger(source, self._doReadOrWrite, source, condition)
283
self.simulate() # fire Twisted timers
284
return 1 # 1=don't auto-remove the source
289
Run simulation loops and reschedule callbacks.
291
if self._simtag is not None:
292
gobject.source_remove(self._simtag)
293
self.runUntilCurrent()
294
timeout = min(self.timeout(), 0.1)
298
self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
302
class PortableGtkReactor(selectreactor.SelectReactor):
304
Reactor that works on Windows.
306
Sockets aren't supported by GTK+'s input_add on Win32.
311
selectreactor.SelectReactor.crash(self)
313
# mainquit is deprecated in newer versions
315
if hasattr(gtk, 'main_quit'):
321
def run(self, installSignalHandlers=1):
323
self.startRunning(installSignalHandlers=installSignalHandlers)
324
gobject.timeout_add(0, self.simulate)
325
# mainloop is deprecated in newer versions
326
if hasattr(gtk, 'main'):
334
Run simulation loops and reschedule callbacks.
336
if self._simtag is not None:
337
gobject.source_remove(self._simtag)
339
timeout = min(self.timeout(), 0.1)
343
self._simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
347
def install(useGtk=True):
349
Configure the twisted mainloop to be run inside the gtk mainloop.
351
@param useGtk: should glib rather than GTK+ event loop be
352
used (this will be slightly faster but does not support GUI).
354
reactor = Gtk2Reactor(useGtk)
355
from twisted.internet.main import installReactor
356
installReactor(reactor)
361
def portableInstall(useGtk=True):
363
Configure the twisted mainloop to be run inside the gtk mainloop.
365
reactor = PortableGtkReactor()
366
from twisted.internet.main import installReactor
367
installReactor(reactor)
372
if runtime.platform.getType() != 'posix':
373
install = portableInstall
377
__all__ = ['install']