1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
6
This module provides support for Twisted to interact with the glib/gtk2 mainloop.
8
In order to use this support, simply do the following::
10
| from twisted.internet import gtk2reactor
11
| gtk2reactor.install()
13
Then use twisted.internet APIs as usual. The other methods here are not
14
intended to be called directly.
16
When installing the reactor, you can choose whether to use the glib
17
event loop or the GTK+ event loop which is based on it but adds GUI
22
Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
29
from zope.interface import implements
31
if not hasattr(sys, 'frozen'):
32
# Don't want to check this for py2exe
35
except (ImportError, AttributeError):
36
pass # maybe we're using pygtk before this hack existed.
38
if hasattr(gobject, "threads_init"):
39
# recent versions of python-gtk expose this. python-gtk=2.4.1
40
# (wrapping glib-2.4.7) does. python-gtk=2.0.0 (wrapping
41
# glib-2.2.3) does not.
42
gobject.threads_init()
45
from twisted.python import log, threadable, runtime, failure
46
from twisted.internet.interfaces import IReactorFDSet
49
from twisted.internet import main, posixbase, error, selectreactor
53
hasReader = reads.has_key
54
hasWriter = writes.has_key
58
POLL_DISCONNECTED = gobject.IO_HUP | gobject.IO_ERR | gobject.IO_NVAL
60
# glib's iochannel sources won't tell us about any events that we haven't
61
# asked for, even if those events aren't sensible inputs to the poll()
63
INFLAGS = gobject.IO_IN | POLL_DISCONNECTED
64
OUTFLAGS = gobject.IO_OUT | POLL_DISCONNECTED
67
# XXX: gtk.main_quit() (which is used for crash()) raises an exception if
68
# gtk.main_level() == 0; however, all the tests freeze if we use this
69
# function to stop the reactor. what gives? (I believe this may have been
70
# a stupid mistake where I forgot to import gtk here... I will remove this
71
# comment if the tests pass)
76
class Gtk2Reactor(posixbase.PosixReactorBase):
77
"""GTK+-2 event loop reactor.
80
implements(IReactorFDSet)
82
def __init__(self, useGtk=True):
83
self.context = gobject.main_context_default()
84
self.loop = gobject.MainLoop()
85
posixbase.PosixReactorBase.__init__(self)
86
# pre 2.3.91 the glib iteration and mainloop functions didn't release
87
# global interpreter lock, thus breaking thread and signal support.
88
if (hasattr(gobject, "pygtk_version") and gobject.pygtk_version >= (2, 3, 91)
90
self.__pending = self.context.pending
91
self.__iteration = self.context.iteration
92
self.__crash = self.loop.quit
93
self.__run = self.loop.run
96
self.__pending = gtk.events_pending
97
self.__iteration = gtk.main_iteration
98
self.__crash = _our_mainquit
101
# The input_add function in pygtk1 checks for objects with a
102
# 'fileno' method and, if present, uses the result of that method
103
# as the input source. The pygtk2 input_add does not do this. The
104
# function below replicates the pygtk1 functionality.
106
# In addition, pygtk maps gtk.input_add to _gobject.io_add_watch, and
107
# g_io_add_watch() takes different condition bitfields than
108
# gtk_input_add(). We use g_io_add_watch() here in case pygtk fixes this
110
def input_add(self, source, condition, callback):
111
if hasattr(source, 'fileno'):
112
# handle python objects
113
def wrapper(source, condition, real_s=source, real_cb=callback):
114
return real_cb(real_s, condition)
115
return gobject.io_add_watch(source.fileno(), condition, wrapper)
117
return gobject.io_add_watch(source, condition, callback)
119
def addReader(self, reader):
120
if not hasReader(reader):
121
reads[reader] = self.input_add(reader, INFLAGS, self.callback)
123
def addWriter(self, writer):
124
if not hasWriter(writer):
125
writes[writer] = self.input_add(writer, OUTFLAGS, self.callback)
128
return self._removeAll(reads, writes)
130
def removeReader(self, reader):
131
if hasReader(reader):
132
gobject.source_remove(reads[reader])
135
def removeWriter(self, writer):
136
if hasWriter(writer):
137
gobject.source_remove(writes[writer])
140
doIterationTimer = None
142
def doIterationTimeout(self, *args):
143
self.doIterationTimer = None
144
return 0 # auto-remove
146
def doIteration(self, delay):
147
# flush some pending events, return if there was something to do
148
# don't use the usual "while self.context.pending(): self.context.iteration()"
149
# idiom because lots of IO (in particular test_tcp's
150
# ProperlyCloseFilesTestCase) can keep us from ever exiting.
151
log.msg(channel='system', event='iteration', reactor=self)
155
# nothing to do, must delay
157
return # shouldn't delay, so just return
158
self.doIterationTimer = gobject.timeout_add(int(delay * 1000),
159
self.doIterationTimeout)
160
# This will either wake up from IO or from a timeout.
161
self.__iteration(1) # block
162
# note: with the .simulate timer below, delays > 0.1 will always be
163
# woken up by the .simulate timer
164
if self.doIterationTimer:
165
# if woken by IO, need to cancel the timer
166
gobject.source_remove(self.doIterationTimer)
167
self.doIterationTimer = None
172
def run(self, installSignalHandlers=1):
173
self.startRunning(installSignalHandlers=installSignalHandlers)
174
gobject.timeout_add(0, self.simulate)
177
def _doReadOrWrite(self, source, condition, faildict={
178
error.ConnectionDone: failure.Failure(error.ConnectionDone()),
179
error.ConnectionLost: failure.Failure(error.ConnectionLost()),
183
if condition & POLL_DISCONNECTED and \
184
not (condition & gobject.IO_IN):
185
why = main.CONNECTION_LOST
188
if condition & gobject.IO_IN:
189
why = source.doRead()
190
didRead = source.doRead
191
if not why and condition & gobject.IO_OUT:
192
# if doRead caused connectionLost, don't call doWrite
193
# if doRead is doWrite, don't call it again.
194
if not source.disconnected and source.doWrite != didRead:
195
why = source.doWrite()
196
didRead = source.doWrite # if failed it was in write
198
why = sys.exc_info()[1]
199
log.msg('Error In %s' % source)
203
self._disconnectSelectable(source, why, didRead == source.doRead)
205
def callback(self, source, condition):
206
log.callWithLogger(source, self._doReadOrWrite, source, condition)
207
self.simulate() # fire Twisted timers
208
return 1 # 1=don't auto-remove the source
211
"""Run simulation loops and reschedule callbacks.
214
if _simtag is not None:
215
gobject.source_remove(_simtag)
216
self.runUntilCurrent()
217
timeout = min(self.timeout(), 0.1)
221
_simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
224
class PortableGtkReactor(selectreactor.SelectReactor):
225
"""Reactor that works on Windows.
227
input_add is not supported on GTK+ for Win32, apparently.
232
# mainquit is deprecated in newer versions
233
if hasattr(gtk, 'main_quit'):
238
def run(self, installSignalHandlers=1):
240
self.startRunning(installSignalHandlers=installSignalHandlers)
242
# mainloop is deprecated in newer versions
243
if hasattr(gtk, 'main'):
249
"""Run simulation loops and reschedule callbacks.
252
if _simtag is not None:
253
gobject.source_remove(_simtag)
255
timeout = min(self.timeout(), 0.1)
259
_simtag = gobject.timeout_add(int(timeout * 1010), self.simulate)
262
def install(useGtk=True):
263
"""Configure the twisted mainloop to be run inside the gtk mainloop.
265
@param useGtk: should glib rather than GTK+ event loop be
266
used (this will be slightly faster but does not support GUI).
268
reactor = Gtk2Reactor(useGtk)
269
from twisted.internet.main import installReactor
270
installReactor(reactor)
273
def portableInstall(useGtk=True):
274
"""Configure the twisted mainloop to be run inside the gtk mainloop.
276
reactor = PortableGtkReactor()
277
from twisted.internet.main import installReactor
278
installReactor(reactor)
281
if runtime.platform.getType() != 'posix':
282
install = portableInstall