File indexing completed on 2024-04-21 04:01:52
0001 # -*- coding: utf-8 -*- 0002 # pylint: skip-file 0003 0004 """ 0005 Copyright (c) 2001-2011 Twisted Matrix Laboratories <twisted-python@twistedmatrix.com> 0006 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de> 0007 0008 SPDX-License-Identifier: GPL-2.0 0009 0010 """ 0011 0012 """ 0013 This module provides support for Twisted to be driven by the Qt mainloop. 0014 0015 In order to use this support, simply do the following:: 0016 | app = QApplication(sys.argv) # your code to init Qt 0017 | import qtreactor 0018 | qtreactor.install() 0019 0020 alternatively: 0021 0022 | from twisted.application import reactors 0023 | reactors.installReactor('qt') 0024 0025 Then use twisted.internet APIs as usual. The other methods here are not 0026 intended to be called directly. 0027 0028 If you don't instantiate a QApplication or QCoreApplication prior to 0029 installing the reactor, a QCoreApplication will be constructed 0030 by the reactor. QCoreApplication does not require a GUI so trial testing 0031 can occur normally. 0032 0033 Twisted can be initialized after QApplication.exec_() with a call to 0034 reactor.runReturn(). calling reactor.stop() will unhook twisted but 0035 leave your Qt application running 0036 0037 API Stability: stable 0038 0039 Maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>} 0040 0041 Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} 0042 Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>} 0043 Subsequent port by therve 0044 """ 0045 0046 import sys 0047 from zope.interface import implementer 0048 from twisted.internet.interfaces import IReactorFDSet 0049 from twisted.python import log, runtime 0050 from twisted.internet import posixbase 0051 0052 from qt import QSocketNotifier, QObject, QTimer, QCoreApplication 0053 from qt import QEventLoop 0054 0055 0056 class TwistedSocketNotifier(QObject): 0057 0058 """ 0059 Connection between an fd event and reader/writer callbacks. 0060 """ 0061 0062 def __init__(self, parent, reactor, watcher, socketType): 0063 QObject.__init__(self, parent) 0064 self.reactor = reactor 0065 self.watcher = watcher 0066 fd = watcher.fileno() 0067 self.notifier = QSocketNotifier(fd, socketType, parent) 0068 self.notifier.setEnabled(True) 0069 if socketType == QSocketNotifier.Read: 0070 self.fn = self.read 0071 else: 0072 self.fn = self.write 0073 self.notifier.activated.connect(self.fn) 0074 0075 def shutdown(self): 0076 self.notifier.setEnabled(False) 0077 self.notifier.activated.disconnect(self.fn) 0078 self.fn = self.watcher = None 0079 self.notifier.deleteLater() 0080 self.deleteLater() 0081 0082 def read(self, fd): 0083 if not self.watcher: 0084 return 0085 w = self.watcher 0086 # doRead can cause self.shutdown to be called so keep a reference to 0087 # self.watcher 0088 0089 def _read(): 0090 # Don't call me again, until the data has been read 0091 self.notifier.setEnabled(False) 0092 why = None 0093 try: 0094 why = w.doRead() 0095 inRead = True 0096 except: 0097 inRead = False 0098 log.err() 0099 why = sys.exc_info()[1] 0100 if why: 0101 self.reactor._disconnectSelectable(w, why, inRead) 0102 elif self.watcher: 0103 self.notifier.setEnabled( 0104 True) # Re enable notification following successful read 0105 self.reactor._iterate(fromqt=True) 0106 log.callWithLogger(w, _read) 0107 0108 def write(self, sock): 0109 if not self.watcher: 0110 return 0111 w = self.watcher 0112 0113 def _write(): 0114 why = None 0115 self.notifier.setEnabled(False) 0116 0117 try: 0118 why = w.doWrite() 0119 except: 0120 log.err() 0121 why = sys.exc_info()[1] 0122 if why: 0123 self.reactor._disconnectSelectable(w, why, False) 0124 elif self.watcher: 0125 self.notifier.setEnabled(True) 0126 self.reactor._iterate(fromqt=True) 0127 log.callWithLogger(w, _write) 0128 0129 0130 @implementer(IReactorFDSet) 0131 class QtReactor(posixbase.PosixReactorBase): 0132 0133 def __init__(self): 0134 self._reads = {} 0135 self._writes = {} 0136 self._notifiers = {} 0137 self._timer = QTimer() 0138 self._timer.setSingleShot(True) 0139 self._timer.timeout.connect(self.iterate) 0140 0141 if QCoreApplication.instance() is None: 0142 # Application Object has not been started yet 0143 self.qApp = QCoreApplication([]) 0144 self._ownApp = True 0145 else: 0146 self.qApp = QCoreApplication.instance() 0147 self._ownApp = False 0148 self._blockApp = None 0149 posixbase.PosixReactorBase.__init__(self) 0150 0151 def _add(self, xer, primary, type): 0152 """ 0153 Private method for adding a descriptor from the event loop. 0154 0155 It takes care of adding it if new or modifying it if already added 0156 for another state (read -> read/write for example). 0157 """ 0158 if xer not in primary: 0159 primary[xer] = TwistedSocketNotifier(None, self, xer, type) 0160 0161 def addReader(self, reader): 0162 """ 0163 Add a FileDescriptor for notification of data available to read. 0164 """ 0165 self._add(reader, self._reads, QSocketNotifier.Read) 0166 0167 def addWriter(self, writer): 0168 """ 0169 Add a FileDescriptor for notification of data available to write. 0170 """ 0171 self._add(writer, self._writes, QSocketNotifier.Write) 0172 0173 def _remove(self, xer, primary): 0174 """ 0175 Private method for removing a descriptor from the event loop. 0176 0177 It does the inverse job of _add, and also add a check in case of the fd 0178 has gone away. 0179 """ 0180 if xer in primary: 0181 notifier = primary.pop(xer) 0182 notifier.shutdown() 0183 0184 def removeReader(self, reader): 0185 """ 0186 Remove a Selectable for notification of data available to read. 0187 """ 0188 self._remove(reader, self._reads) 0189 0190 def removeWriter(self, writer): 0191 """ 0192 Remove a Selectable for notification of data available to write. 0193 """ 0194 self._remove(writer, self._writes) 0195 0196 def removeAll(self): 0197 """ 0198 Remove all selectables, and return a list of them. 0199 """ 0200 rv = self._removeAll(self._reads, self._writes) 0201 return rv 0202 0203 def getReaders(self): 0204 return self._reads.keys() 0205 0206 def getWriters(self): 0207 return self._writes.keys() 0208 0209 def callLater(self, howlong, *args, **kargs): 0210 rval = super(QtReactor, self).callLater(howlong, *args, **kargs) 0211 self.reactorInvocation() 0212 return rval 0213 0214 def reactorInvocation(self): 0215 self._timer.stop() 0216 self._timer.setInterval(0) 0217 self._timer.start() 0218 0219 def _iterate(self, delay=None, fromqt=False): 0220 """See twisted.internet.interfaces.IReactorCore.iterate. 0221 """ 0222 self.runUntilCurrent() 0223 self.doIteration(delay, fromqt) 0224 0225 iterate = _iterate 0226 0227 def doIteration(self, delay=None, fromqt=False): 0228 'This method is called by a Qt timer or by network activity on a file descriptor' 0229 0230 if not self.running and self._blockApp: 0231 self._blockApp.quit() 0232 self._timer.stop() 0233 delay = max(delay or 0, 1) 0234 if not fromqt: 0235 self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000) 0236 if self.timeout() is None: 0237 timeout = 0.1 0238 elif self.timeout() == 0: 0239 timeout = 0 0240 else: 0241 timeout = self.timeout() 0242 self._timer.setInterval(int(timeout * 1000)) 0243 self._timer.start() 0244 0245 def runReturn(self, installSignalHandlers=True): 0246 self.startRunning(installSignalHandlers=installSignalHandlers) 0247 self.reactorInvocation() 0248 0249 def run(self, installSignalHandlers=True): 0250 if self._ownApp: 0251 self._blockApp = self.qApp 0252 else: 0253 self._blockApp = QEventLoop() 0254 self.runReturn() 0255 self._blockApp.exec_() 0256 0257 0258 class QtEventReactor(QtReactor): 0259 0260 def __init__(self, *args, **kwargs): 0261 self._events = {} 0262 super(QtEventReactor, self).__init__() 0263 0264 def addEvent(self, event, fd, action): 0265 """ 0266 Add a new win32 event to the event loop. 0267 """ 0268 self._events[event] = (fd, action) 0269 0270 def removeEvent(self, event): 0271 """ 0272 Remove an event. 0273 """ 0274 if event in self._events: 0275 del self._events[event] 0276 0277 def doEvents(self): 0278 handles = self._events.keys() 0279 if len(handles) > 0: 0280 val = None 0281 while val != WAIT_TIMEOUT: 0282 val = MsgWaitForMultipleObjects( 0283 handles, 0284 0, 0285 0, 0286 QS_ALLINPUT | QS_ALLEVENTS) 0287 if val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles): 0288 event_id = handles[val - WAIT_OBJECT_0] 0289 if event_id in self._events: 0290 fd, action = self._events[event_id] 0291 log.callWithLogger(fd, self._runAction, action, fd) 0292 elif val == WAIT_TIMEOUT: 0293 pass 0294 else: 0295 # print 'Got an unexpected return of %r' % val 0296 return 0297 0298 def _runAction(self, action, fd): 0299 try: 0300 closed = getattr(fd, action)() 0301 except: 0302 closed = sys.exc_info()[1] 0303 log.deferr() 0304 0305 if closed: 0306 self._disconnectSelectable(fd, closed, action == 'doRead') 0307 0308 def timeout(self): 0309 t = super(QtEventReactor, self).timeout() 0310 if t is None: 0311 return 0.0 0312 return min(t, 0.01) 0313 0314 def iterate(self, delay=None): 0315 """See twisted.internet.interfaces.IReactorCore.iterate. 0316 """ 0317 self.runUntilCurrent() 0318 self.doEvents() 0319 self.doIteration(delay) 0320 0321 0322 def posixinstall(): 0323 """ 0324 Install the Qt reactor. 0325 """ 0326 p = QtReactor() 0327 from twisted.internet.main import installReactor 0328 installReactor(p) 0329 0330 0331 def win32install(): 0332 """ 0333 Install the Qt reactor. 0334 """ 0335 p = QtEventReactor() 0336 from twisted.internet.main import installReactor 0337 installReactor(p) 0338 0339 0340 if runtime.platform.getType() == 'win32': 0341 install = win32install 0342 else: 0343 install = posixinstall 0344 0345 0346 __all__ = ["install"]