1 # Copyright (c) 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, deprecate, versions
30 from twisted.internet.interfaces import IReactorFDSet
33 from twisted.internet import posixbase, selectreactor
36 deprecatedSince = versions.Version("Twisted", 10, 1, 0)
37 deprecationMessage = ("All new applications should be written with gtk 2.x, "
38 "which is supported by twisted.internet.gtk2reactor.")
41 class GtkReactor(posixbase.PosixReactorBase):
43 GTK+ event loop reactor.
45 @ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk INPUT_READ
48 @ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
49 INTPUT_WRITE watch handles.
51 @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
53 implements(IReactorFDSet)
55 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
56 __name__, "GtkReactor")
60 Initialize the file descriptor tracking dictionaries and the base
66 posixbase.PosixReactorBase.__init__(self)
69 def addReader(self, reader):
70 if reader not in self._reads:
71 self._reads[reader] = gtk.input_add(reader, gtk.GDK.INPUT_READ, self.callback)
73 def addWriter(self, writer):
74 if writer not in self._writes:
75 self._writes[writer] = gtk.input_add(writer, gtk.GDK.INPUT_WRITE, self.callback)
79 return self._reads.keys()
83 return self._writes.keys()
87 return self._removeAll(self._reads, self._writes)
90 def removeReader(self, reader):
91 if reader in self._reads:
92 gtk.input_remove(self._reads[reader])
93 del self._reads[reader]
95 def removeWriter(self, writer):
96 if writer in self._writes:
97 gtk.input_remove(self._writes[writer])
98 del self._writes[writer]
100 doIterationTimer = None
102 def doIterationTimeout(self, *args):
103 self.doIterationTimer = None
104 return 0 # auto-remove
105 def doIteration(self, delay):
106 # flush some pending events, return if there was something to do
107 # don't use the usual "while gtk.events_pending(): mainiteration()"
108 # idiom because lots of IO (in particular test_tcp's
109 # ProperlyCloseFilesTestCase) can keep us from ever exiting.
110 log.msg(channel='system', event='iteration', reactor=self)
111 if gtk.events_pending():
114 # nothing to do, must delay
116 return # shouldn't delay, so just return
117 self.doIterationTimer = gtk.timeout_add(int(delay * 1000),
118 self.doIterationTimeout)
119 # This will either wake up from IO or from a timeout.
120 gtk.mainiteration(1) # block
121 # note: with the .simulate timer below, delays > 0.1 will always be
122 # woken up by the .simulate timer
123 if self.doIterationTimer:
124 # if woken by IO, need to cancel the timer
125 gtk.timeout_remove(self.doIterationTimer)
126 self.doIterationTimer = None
129 posixbase.PosixReactorBase.crash(self)
132 def run(self, installSignalHandlers=1):
133 self.startRunning(installSignalHandlers=installSignalHandlers)
134 gtk.timeout_add(0, self.simulate)
137 def _readAndWrite(self, source, condition):
138 # note: gtk-1.2's gtk_input_add presents an API in terms of gdk
139 # constants like INPUT_READ and INPUT_WRITE. Internally, it will add
140 # POLL_HUP and POLL_ERR to the poll() events, but if they happen it
141 # will turn them back into INPUT_READ and INPUT_WRITE. gdkevents.c
142 # maps IN/HUP/ERR to INPUT_READ, and OUT/ERR to INPUT_WRITE. This
143 # means there is no immediate way to detect a disconnected socket.
145 # The g_io_add_watch() API is more suited to this task. I don't think
146 # pygtk exposes it, though.
150 if condition & gtk.GDK.INPUT_READ:
151 why = source.doRead()
152 didRead = source.doRead
153 if not why and condition & gtk.GDK.INPUT_WRITE:
154 # if doRead caused connectionLost, don't call doWrite
155 # if doRead is doWrite, don't call it again.
156 if not source.disconnected and source.doWrite != didRead:
157 why = source.doWrite()
158 didRead = source.doWrite # if failed it was in write
160 why = sys.exc_info()[1]
161 log.msg('Error In %s' % source)
165 self._disconnectSelectable(source, why, didRead == source.doRead)
167 def callback(self, source, condition):
168 log.callWithLogger(source, self._readAndWrite, source, condition)
169 self.simulate() # fire Twisted timers
170 return 1 # 1=don't auto-remove the source
173 """Run simulation loops and reschedule callbacks.
175 if self._simtag is not None:
176 gtk.timeout_remove(self._simtag)
177 self.runUntilCurrent()
178 timeout = min(self.timeout(), 0.1)
181 # Quoth someone other than me, "grumble", yet I know not why. Try to be
182 # more specific in your complaints, guys. -exarkun
183 self._simtag = gtk.timeout_add(int(timeout * 1010), self.simulate)
187 class PortableGtkReactor(selectreactor.SelectReactor):
188 """Reactor that works on Windows.
190 input_add is not supported on GTK+ for Win32, apparently.
192 @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
196 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
197 __name__, "PortableGtkReactor")
200 selectreactor.SelectReactor.crash(self)
203 def run(self, installSignalHandlers=1):
204 self.startRunning(installSignalHandlers=installSignalHandlers)
209 """Run simulation loops and reschedule callbacks.
211 if self._simtag is not None:
212 gtk.timeout_remove(self._simtag)
214 timeout = min(self.timeout(), 0.1)
218 # See comment for identical line in GtkReactor.simulate.
219 self._simtag = gtk.timeout_add((timeout * 1010), self.simulate)
224 """Configure the twisted mainloop to be run inside the gtk mainloop.
226 reactor = GtkReactor()
227 from twisted.internet.main import installReactor
228 installReactor(reactor)
231 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
235 def portableInstall():
236 """Configure the twisted mainloop to be run inside the gtk mainloop.
238 reactor = PortableGtkReactor()
239 from twisted.internet.main import installReactor
240 installReactor(reactor)
243 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
244 __name__, "portableInstall")
247 if runtime.platform.getType() != 'posix':
248 install = portableInstall
250 __all__ = ['install']