update the qt4reactor
[smewt:smewt.git] / smewt / plugins / qt4reactor.py
1 # Copyright (c) 2001-2011 Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4
5 """
6 This module provides support for Twisted to be driven by the Qt mainloop.
7
8 In order to use this support, simply do the following::
9     |  app = QApplication(sys.argv) # your code to init Qt
10     |  import qt4reactor
11     |  qt4reactor.install()
12     
13 alternatively:
14
15     |  from twisted.application import reactors
16     |  reactors.installReactor('qt4')
17
18 Then use twisted.internet APIs as usual.  The other methods here are not
19 intended to be called directly.
20
21 If you don't instantiate a QApplication or QCoreApplication prior to
22 installing the reactor, a QCoreApplication will be constructed
23 by the reactor.  QCoreApplication does not require a GUI so trial testing
24 can occur normally.
25
26 Twisted can be initialized after QApplication.exec_() with a call to
27 reactor.runReturn().  calling reactor.stop() will unhook twisted but
28 leave your Qt application running
29
30 API Stability: stable
31
32 Maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>}
33
34 Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>}
35 Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>}
36 Subsequent port by therve
37 """
38
39 import sys
40 import time
41 from zope.interface import implements
42 from twisted.internet.interfaces import IReactorFDSet
43 from twisted.python import log, runtime
44 from twisted.internet import posixbase
45 from twisted.python.runtime import platformType, platform
46
47 try:
48     from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
49     from PyQt4.QtCore import QEventLoop
50 except ImportError:
51     from PySide.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer, QCoreApplication
52     from PySide.QtCore import QEventLoop
53
54
55 class TwistedSocketNotifier(QObject):
56     """
57     Connection between an fd event and reader/writer callbacks.
58     """
59
60     def __init__(self, parent, reactor, watcher, socketType):
61         QObject.__init__(self, parent)
62         self.reactor = reactor
63         self.watcher = watcher
64         fd = watcher.fileno()
65         self.notifier = QSocketNotifier(fd, socketType, parent)
66         self.notifier.setEnabled(True)
67         if socketType == QSocketNotifier.Read:
68             self.fn = self.read
69         else:
70             self.fn = self.write
71         QObject.connect(self.notifier, SIGNAL("activated(int)"), self.fn)
72
73
74     def shutdown(self):
75         self.notifier.setEnabled(False)
76         self.disconnect(self.notifier, SIGNAL("activated(int)"), self.fn)
77         self.fn = self.watcher = None
78         self.notifier.deleteLater()
79         self.deleteLater()
80
81
82     def read(self, fd):
83         if not self.watcher:
84             return
85         w = self.watcher
86         # doRead can cause self.shutdown to be called so keep a reference to self.watcher
87         def _read():
88             #Don't call me again, until the data has been read
89             self.notifier.setEnabled(False)
90             why = None
91             try:
92                 why = w.doRead()
93                 inRead = True
94             except:
95                 inRead = False
96                 log.err()
97                 why = sys.exc_info()[1]
98             if why:
99                 self.reactor._disconnectSelectable(w, why, inRead)
100             elif self.watcher:
101                 self.notifier.setEnabled(True) # Re enable notification following sucessfull read
102             self.reactor._iterate(fromqt=True)
103         log.callWithLogger(w, _read)
104
105     def write(self, sock):
106         if not self.watcher:
107             return
108         w = self.watcher
109         def _write():
110             why = None
111             self.notifier.setEnabled(False)
112             
113             try:
114                 why = w.doWrite()
115             except:
116                 log.err()
117                 why = sys.exc_info()[1]
118             if why:
119                 self.reactor._disconnectSelectable(w, why, False)
120             elif self.watcher:
121                 self.notifier.setEnabled(True)
122             self.reactor._iterate(fromqt=True)
123         log.callWithLogger(w, _write)
124
125
126
127 class QtReactor(posixbase.PosixReactorBase):
128     implements(IReactorFDSet)
129
130     def __init__(self):
131         self._reads = {}
132         self._writes = {}
133         self._notifiers = {}
134         self._timer = QTimer()
135         self._timer.setSingleShot(True)
136         QObject.connect(self._timer, SIGNAL("timeout()"), self.iterate)
137
138         if QCoreApplication.startingUp():
139             # Application Object has not been started yet
140             self.qApp=QCoreApplication([])
141             self._ownApp=True
142         else:
143             self.qApp = QCoreApplication.instance()
144             self._ownApp=False
145         self._blockApp = None
146         posixbase.PosixReactorBase.__init__(self)
147
148
149     def _add(self, xer, primary, type):
150         """
151         Private method for adding a descriptor from the event loop.
152
153         It takes care of adding it if  new or modifying it if already added
154         for another state (read -> read/write for example).
155         """
156         if xer not in primary:
157             primary[xer] = TwistedSocketNotifier(None, self, xer, type)
158
159
160     def addReader(self, reader):
161         """
162         Add a FileDescriptor for notification of data available to read.
163         """
164         self._add(reader, self._reads, QSocketNotifier.Read)
165
166
167     def addWriter(self, writer):
168         """
169         Add a FileDescriptor for notification of data available to write.
170         """
171         self._add(writer, self._writes, QSocketNotifier.Write)
172
173
174     def _remove(self, xer, primary):
175         """
176         Private method for removing a descriptor from the event loop.
177
178         It does the inverse job of _add, and also add a check in case of the fd
179         has gone away.
180         """
181         if xer in primary:
182             notifier = primary.pop(xer)
183             notifier.shutdown()
184
185         
186     def removeReader(self, reader):
187         """
188         Remove a Selectable for notification of data available to read.
189         """
190         self._remove(reader, self._reads)
191
192
193     def removeWriter(self, writer):
194         """
195         Remove a Selectable for notification of data available to write.
196         """
197         self._remove(writer, self._writes)
198
199
200     def removeAll(self):
201         """
202         Remove all selectables, and return a list of them.
203         """
204         rv = self._removeAll(self._reads, self._writes)
205         return rv
206
207
208     def getReaders(self):
209         return self._reads.keys()
210
211
212     def getWriters(self):
213         return self._writes.keys()
214
215
216     def callLater(self,howlong, *args, **kargs):
217         rval = super(QtReactor,self).callLater(howlong, *args, **kargs)
218         self.reactorInvocation()
219         return rval
220
221
222     def reactorInvocation(self):
223         self._timer.stop()
224         self._timer.setInterval(0)
225         self._timer.start()
226         
227
228     def _iterate(self, delay=None, fromqt=False):
229         """See twisted.internet.interfaces.IReactorCore.iterate.
230         """
231         self.runUntilCurrent()
232         self.doIteration(delay, fromqt)
233
234     iterate = _iterate
235
236     def doIteration(self, delay=None, fromqt=False):
237         'This method is called by a Qt timer or by network activity on a file descriptor'
238         
239         if not self.running and self._blockApp:
240             self._blockApp.quit()
241         self._timer.stop()
242         delay = max(delay, 1)
243         if not fromqt:
244             self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000)
245         if self.timeout() is None:
246             timeout = 0.1
247         elif self.timeout() == 0:
248             timeout = 0
249         else:
250             timeout = self.timeout()
251         self._timer.setInterval(timeout * 1000)
252         self._timer.start()
253
254
255     def runReturn(self, installSignalHandlers=True):
256         self.startRunning(installSignalHandlers=installSignalHandlers)
257         self.reactorInvocation()
258
259
260     def run(self, installSignalHandlers=True):
261         if self._ownApp:
262             self._blockApp = self.qApp
263         else:
264             self._blockApp = QEventLoop()
265         self.runReturn()
266         self._blockApp.exec_()
267
268
269 class QtEventReactor(QtReactor):
270     def __init__(self, *args, **kwargs):
271         self._events = {}
272         super(QtEventReactor, self).__init__()
273
274         
275     def addEvent(self, event, fd, action):
276         """
277         Add a new win32 event to the event loop.
278         """
279         self._events[event] = (fd, action)
280
281
282     def removeEvent(self, event):
283         """
284         Remove an event.
285         """
286         if event in self._events:
287             del self._events[event]
288
289
290     def doEvents(self):
291         handles = self._events.keys()
292         if len(handles) > 0:
293             val = None
294             while val != WAIT_TIMEOUT:
295                 val = MsgWaitForMultipleObjects(handles, 0, 0, QS_ALLINPUT | QS_ALLEVENTS)
296                 if val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles):
297                     event_id = handles[val - WAIT_OBJECT_0]
298                     if event_id in self._events:
299                         fd, action = self._events[event_id]
300                         log.callWithLogger(fd, self._runAction, action, fd)
301                 elif val == WAIT_TIMEOUT:
302                     pass
303                 else:
304                     #print 'Got an unexpected return of %r' % val
305                     return
306
307
308     def _runAction(self, action, fd):
309         try:
310             closed = getattr(fd, action)()
311         except:
312             closed = sys.exc_info()[1]
313             log.deferr()
314
315         if closed:
316             self._disconnectSelectable(fd, closed, action == 'doRead')
317
318             
319     def timeout(self):
320         t = super(QtEventReactor, self).timeout()
321         return min(t, 0.01)
322
323
324     def iterate(self, delay=None):
325         """See twisted.internet.interfaces.IReactorCore.iterate.
326         """
327         self.runUntilCurrent()
328         self.doEvents()
329         self.doIteration(delay)
330
331
332 def posixinstall():
333     """
334     Install the Qt reactor.
335     """
336     p = QtReactor()
337     from twisted.internet.main import installReactor
338     installReactor(p)
339
340
341 def win32install():
342     """
343     Install the Qt reactor.
344     """
345     p = QtEventReactor()
346     from twisted.internet.main import installReactor
347     installReactor(p)
348
349
350 if runtime.platform.getType() == 'win32':
351     from win32event import CreateEvent, MsgWaitForMultipleObjects
352     from win32event import WAIT_OBJECT_0, WAIT_TIMEOUT, QS_ALLINPUT, QS_ALLEVENTS
353     install = win32install
354 else:
355     install = posixinstall
356
357
358 __all__ = ["install"]
359