Imported Upstream version 12.1.0
[contrib/python-twisted.git] / twisted / internet / gtkreactor.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 This module provides support for Twisted to interact with the PyGTK mainloop.
6
7 In order to use this support, simply do the following::
8
9     |  from twisted.internet import gtkreactor
10     |  gtkreactor.install()
11
12 Then use twisted.internet APIs as usual.  The other methods here are not
13 intended to be called directly.
14 """
15
16 import sys
17
18 # System Imports
19 try:
20     import pygtk
21     pygtk.require('1.2')
22 except ImportError, AttributeError:
23     pass # maybe we're using pygtk before this hack existed.
24 import gtk
25
26 from zope.interface import implements
27
28 # Twisted Imports
29 from twisted.python import log, runtime, deprecate, versions
30 from twisted.internet.interfaces import IReactorFDSet
31
32 # Sibling Imports
33 from twisted.internet import posixbase, selectreactor
34
35
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.")
39
40
41 class GtkReactor(posixbase.PosixReactorBase):
42     """
43     GTK+ event loop reactor.
44
45     @ivar _reads: A dictionary mapping L{FileDescriptor} instances to gtk INPUT_READ
46         watch handles.
47
48     @ivar _writes: A dictionary mapping L{FileDescriptor} instances to gtk
49         INTPUT_WRITE watch handles.
50
51     @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
52     """
53     implements(IReactorFDSet)
54
55     deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
56                                         __name__, "GtkReactor")
57
58     def __init__(self):
59         """
60         Initialize the file descriptor tracking dictionaries and the base
61         class.
62         """
63         self._simtag = None
64         self._reads = {}
65         self._writes = {}
66         posixbase.PosixReactorBase.__init__(self)
67
68
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)
72
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)
76
77
78     def getReaders(self):
79         return self._reads.keys()
80
81
82     def getWriters(self):
83         return self._writes.keys()
84
85
86     def removeAll(self):
87         return self._removeAll(self._reads, self._writes)
88
89
90     def removeReader(self, reader):
91         if reader in self._reads:
92             gtk.input_remove(self._reads[reader])
93             del self._reads[reader]
94
95     def removeWriter(self, writer):
96         if writer in self._writes:
97             gtk.input_remove(self._writes[writer])
98             del self._writes[writer]
99
100     doIterationTimer = None
101
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():
112             gtk.mainiteration(0)
113             return
114         # nothing to do, must delay
115         if delay == 0:
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
127
128     def crash(self):
129         posixbase.PosixReactorBase.crash(self)
130         gtk.mainquit()
131
132     def run(self, installSignalHandlers=1):
133         self.startRunning(installSignalHandlers=installSignalHandlers)
134         gtk.timeout_add(0, self.simulate)
135         gtk.mainloop()
136
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.
144
145         # The g_io_add_watch() API is more suited to this task. I don't think
146         # pygtk exposes it, though.
147         why = None
148         didRead = None
149         try:
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
159         except:
160             why = sys.exc_info()[1]
161             log.msg('Error In %s' % source)
162             log.deferr()
163
164         if why:
165             self._disconnectSelectable(source, why, didRead == source.doRead)
166
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
171
172     def simulate(self):
173         """Run simulation loops and reschedule callbacks.
174         """
175         if self._simtag is not None:
176             gtk.timeout_remove(self._simtag)
177         self.runUntilCurrent()
178         timeout = min(self.timeout(), 0.1)
179         if timeout is None:
180             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)
184
185
186
187 class PortableGtkReactor(selectreactor.SelectReactor):
188     """Reactor that works on Windows.
189
190     input_add is not supported on GTK+ for Win32, apparently.
191
192     @ivar _simtag: A gtk timeout handle for the next L{simulate} call.
193     """
194     _simtag = None
195
196     deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
197                                         __name__, "PortableGtkReactor")
198
199     def crash(self):
200         selectreactor.SelectReactor.crash(self)
201         gtk.mainquit()
202
203     def run(self, installSignalHandlers=1):
204         self.startRunning(installSignalHandlers=installSignalHandlers)
205         self.simulate()
206         gtk.mainloop()
207
208     def simulate(self):
209         """Run simulation loops and reschedule callbacks.
210         """
211         if self._simtag is not None:
212             gtk.timeout_remove(self._simtag)
213         self.iterate()
214         timeout = min(self.timeout(), 0.1)
215         if timeout is None:
216             timeout = 0.1
217
218         # See comment for identical line in GtkReactor.simulate.
219         self._simtag = gtk.timeout_add((timeout * 1010), self.simulate)
220
221
222
223 def install():
224     """Configure the twisted mainloop to be run inside the gtk mainloop.
225     """
226     reactor = GtkReactor()
227     from twisted.internet.main import installReactor
228     installReactor(reactor)
229     return reactor
230
231 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
232                                     __name__, "install")
233
234
235 def portableInstall():
236     """Configure the twisted mainloop to be run inside the gtk mainloop.
237     """
238     reactor = PortableGtkReactor()
239     from twisted.internet.main import installReactor
240     installReactor(reactor)
241     return reactor
242
243 deprecate.deprecatedModuleAttribute(deprecatedSince, deprecationMessage,
244                                     __name__, "portableInstall")
245
246
247 if runtime.platform.getType() != 'posix':
248     install = portableInstall
249
250 __all__ = ['install']