1
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
2
# See LICENSE for details.
5
This module provides support for Twisted to interact with the PyGTK mainloop.
7
In order to use this support, simply do the following::
9
| from twisted.internet import gtkreactor
10
| gtkreactor.install()
12
Then use twisted.internet APIs as usual. The other methods here are not
13
intended to be called directly.
22
except ImportError, AttributeError:
23
pass # maybe we're using pygtk before this hack existed.
26
from zope.interface import implements
29
from twisted.python import log, runtime
30
from twisted.internet.interfaces import IReactorFDSet
33
from twisted.internet import posixbase, selectreactor
36
class GtkReactor(posixbase.PosixReactorBase):
38
GTK+ event loop reactor.
40
@ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk INPUT_READ
43
@ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
44
INTPUT_WRITE watch handles.
46
@ivar _simtag: A gtk timeout handle for the next L{simulate} call.
48
implements(IReactorFDSet)
52
Initialize the file descriptor tracking dictionaries and the base
58
posixbase.PosixReactorBase.__init__(self)
61
def addReader(self, reader):
62
if reader not in self._reads:
63
self._reads[reader] = gtk.input_add(reader, gtk.GDK.INPUT_READ, self.callback)
65
def addWriter(self, writer):
66
if writer not in self._writes:
67
self._writes[writer] = gtk.input_add(writer, gtk.GDK.INPUT_WRITE, self.callback)
71
return self._reads.keys()
75
return self._writes.keys()
79
return self._removeAll(self._reads, self._writes)
82
def removeReader(self, reader):
83
if reader in self._reads:
84
gtk.input_remove(self._reads[reader])
85
del self._reads[reader]
87
def removeWriter(self, writer):
88
if writer in self._writes:
89
gtk.input_remove(self._writes[writer])
90
del self._writes[writer]
92
doIterationTimer = None
94
def doIterationTimeout(self, *args):
95
self.doIterationTimer = None
96
return 0 # auto-remove
97
def doIteration(self, delay):
98
# flush some pending events, return if there was something to do
99
# don't use the usual "while gtk.events_pending(): mainiteration()"
100
# idiom because lots of IO (in particular test_tcp's
101
# ProperlyCloseFilesTestCase) can keep us from ever exiting.
102
log.msg(channel='system', event='iteration', reactor=self)
103
if gtk.events_pending():
106
# nothing to do, must delay
108
return # shouldn't delay, so just return
109
self.doIterationTimer = gtk.timeout_add(int(delay * 1000),
110
self.doIterationTimeout)
111
# This will either wake up from IO or from a timeout.
112
gtk.mainiteration(1) # block
113
# note: with the .simulate timer below, delays > 0.1 will always be
114
# woken up by the .simulate timer
115
if self.doIterationTimer:
116
# if woken by IO, need to cancel the timer
117
gtk.timeout_remove(self.doIterationTimer)
118
self.doIterationTimer = None
121
posixbase.PosixReactorBase.crash(self)
124
def run(self, installSignalHandlers=1):
125
self.startRunning(installSignalHandlers=installSignalHandlers)
126
gtk.timeout_add(0, self.simulate)
129
def _readAndWrite(self, source, condition):
130
# note: gtk-1.2's gtk_input_add presents an API in terms of gdk
131
# constants like INPUT_READ and INPUT_WRITE. Internally, it will add
132
# POLL_HUP and POLL_ERR to the poll() events, but if they happen it
133
# will turn them back into INPUT_READ and INPUT_WRITE. gdkevents.c
134
# maps IN/HUP/ERR to INPUT_READ, and OUT/ERR to INPUT_WRITE. This
135
# means there is no immediate way to detect a disconnected socket.
137
# The g_io_add_watch() API is more suited to this task. I don't think
138
# pygtk exposes it, though.
142
if condition & gtk.GDK.INPUT_READ:
143
why = source.doRead()
144
didRead = source.doRead
145
if not why and condition & gtk.GDK.INPUT_WRITE:
146
# if doRead caused connectionLost, don't call doWrite
147
# if doRead is doWrite, don't call it again.
148
if not source.disconnected and source.doWrite != didRead:
149
why = source.doWrite()
150
didRead = source.doWrite # if failed it was in write
152
why = sys.exc_info()[1]
153
log.msg('Error In %s' % source)
157
self._disconnectSelectable(source, why, didRead == source.doRead)
159
def callback(self, source, condition):
160
log.callWithLogger(source, self._readAndWrite, source, condition)
161
self.simulate() # fire Twisted timers
162
return 1 # 1=don't auto-remove the source
165
"""Run simulation loops and reschedule callbacks.
167
if self._simtag is not None:
168
gtk.timeout_remove(self._simtag)
169
self.runUntilCurrent()
170
timeout = min(self.timeout(), 0.1)
173
# Quoth someone other than me, "grumble", yet I know not why. Try to be
174
# more specific in your complaints, guys. -exarkun
175
self._simtag = gtk.timeout_add(int(timeout * 1010), self.simulate)
179
class PortableGtkReactor(selectreactor.SelectReactor):
180
"""Reactor that works on Windows.
182
input_add is not supported on GTK+ for Win32, apparently.
184
@ivar _simtag: A gtk timeout handle for the next L{simulate} call.
190
selectreactor.SelectReactor.crash(self)
193
def run(self, installSignalHandlers=1):
194
self.startRunning(installSignalHandlers=installSignalHandlers)
199
"""Run simulation loops and reschedule callbacks.
201
if self._simtag is not None:
202
gtk.timeout_remove(self._simtag)
204
timeout = min(self.timeout(), 0.1)
208
# See comment for identical line in GtkReactor.simulate.
209
self._simtag = gtk.timeout_add((timeout * 1010), self.simulate)
214
"""Configure the twisted mainloop to be run inside the gtk mainloop.
216
reactor = GtkReactor()
217
from twisted.internet.main import installReactor
218
installReactor(reactor)
221
def portableInstall():
222
"""Configure the twisted mainloop to be run inside the gtk mainloop.
224
reactor = PortableGtkReactor()
225
from twisted.internet.main import installReactor
226
installReactor(reactor)
229
if runtime.platform.getType() != 'posix':
230
install = portableInstall
232
__all__ = ['install']