1# -*- coding: utf-8 -*- 2# Copyright (c) 2001-2018 3# Allen Short 4# Andy Gayton 5# Andrew Bennetts 6# Antoine Pitrou 7# Apple Computer, Inc. 8# Ashwini Oruganti 9# bakbuk 10# Benjamin Bruheim 11# Bob Ippolito 12# Burak Nehbit 13# Canonical Limited 14# Christopher Armstrong 15# Christopher R. Wood 16# David Reid 17# Donovan Preston 18# Elvis Stansvik 19# Eric Mangold 20# Eyal Lotem 21# Glenn Tarbox 22# Google Inc. 23# Hybrid Logic Ltd. 24# Hynek Schlawack 25# Itamar Turner-Trauring 26# James Knight 27# Jason A. Mobarak 28# Jean-Paul Calderone 29# Jessica McKellar 30# Jonathan Jacobs 31# Jonathan Lange 32# Jonathan D. Simms 33# Jürgen Hermann 34# Julian Berman 35# Kevin Horn 36# Kevin Turner 37# Kyle Altendorf 38# Laurens Van Houtven 39# Mary Gardiner 40# Matthew Lefkowitz 41# Massachusetts Institute of Technology 42# Moshe Zadka 43# Paul Swartz 44# Pavel Pergamenshchik 45# Ralph Meijer 46# Richard Wall 47# Sean Riley 48# Software Freedom Conservancy 49# Tarashish Mishra 50# Travis B. Hartwell 51# Thijs Triemstra 52# Thomas Herve 53# Timothy Allen 54# Tom Prince 55 56# Permission is hereby granted, free of charge, to any person obtaining 57# a copy of this software and associated documentation files (the 58# "Software"), to deal in the Software without restriction, including 59# without limitation the rights to use, copy, modify, merge, publish, 60# distribute, sublicense, and/or sell copies of the Software, and to 61# permit persons to whom the Software is furnished to do so, subject to 62# the following conditions: 63 64# The above copyright notice and this permission notice shall be 65# included in all copies or substantial portions of the Software. 66 67# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 68# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 69# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 70# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 71# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 72# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 73# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 74 75 76""" 77This module provides support for Twisted to be driven by the Qt mainloop. 78 79In order to use this support, simply do the following:: 80 | app = QApplication(sys.argv) # your code to init Qt 81 | import qt5reactor 82 | qt5reactor.install() 83 84Then use twisted.internet APIs as usual. The other methods here are not 85intended to be called directly. 86 87If you don't instantiate a QApplication or QCoreApplication prior to 88installing the reactor, a QCoreApplication will be constructed 89by the reactor. QCoreApplication does not require a GUI so trial testing 90can occur normally. 91 92Twisted can be initialized after QApplication.exec_() with a call to 93reactor.runReturn(). calling reactor.stop() will unhook twisted but 94leave your Qt application running 95 96Qt5 Port: U{Burak Nehbit<mailto:burak@nehbit.net>} 97 98Current maintainer: U{Christopher R. Wood<mailto:chris@leastauthority.com>} 99 100Previous maintainer: U{Tarashish Mishra<mailto:sunu@sunu.in>} 101Previous maintainer: U{Glenn H Tarbox, PhD<mailto:glenn@tarbox.org>} 102Previous maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} 103Original port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>} 104Subsequent port by therve 105""" 106 107import sys 108 109from syncplay.vendor.Qt.QtCore import ( 110 QCoreApplication, QEventLoop, QObject, QSocketNotifier, QTimer, Signal) 111from twisted.internet import posixbase 112from twisted.internet.interfaces import IReactorFDSet 113from twisted.python import log, runtime 114from zope.interface import implementer 115 116class TwistedSocketNotifier(QObject): 117 """Connection between an fd event and reader/writer callbacks.""" 118 119 activated = Signal(int) 120 121 def __init__(self, parent, reactor, watcher, socketType): 122 QObject.__init__(self, parent) 123 self.reactor = reactor 124 self.watcher = watcher 125 fd = watcher.fileno() 126 self.notifier = QSocketNotifier(fd, socketType, parent) 127 self.notifier.setEnabled(True) 128 if socketType == QSocketNotifier.Read: 129 self.fn = self.read 130 else: 131 self.fn = self.write 132 self.notifier.activated.connect(self.fn) 133 134 def shutdown(self): 135 self.notifier.setEnabled(False) 136 self.notifier.activated.disconnect(self.fn) 137 self.fn = self.watcher = None 138 self.notifier.deleteLater() 139 self.deleteLater() 140 141 def read(self, fd): 142 if not self.watcher: 143 return 144 w = self.watcher 145 # doRead can cause self.shutdown to be called so keep 146 # a reference to self.watcher 147 148 def _read(): 149 # Don't call me again, until the data has been read 150 self.notifier.setEnabled(False) 151 why = None 152 try: 153 why = w.doRead() 154 inRead = True 155 except: 156 inRead = False 157 log.err() 158 why = sys.exc_info()[1] 159 if why: 160 self.reactor._disconnectSelectable(w, why, inRead) 161 elif self.watcher: 162 self.notifier.setEnabled(True) 163 # Re enable notification following sucessfull read 164 self.reactor._iterate(fromqt=True) 165 166 log.callWithLogger(w, _read) 167 168 def write(self, sock): 169 if not self.watcher: 170 return 171 w = self.watcher 172 173 def _write(): 174 why = None 175 self.notifier.setEnabled(False) 176 try: 177 why = w.doWrite() 178 except: 179 log.err() 180 why = sys.exc_info()[1] 181 if why: 182 self.reactor._disconnectSelectable(w, why, False) 183 elif self.watcher: 184 self.notifier.setEnabled(True) 185 self.reactor._iterate(fromqt=True) 186 187 log.callWithLogger(w, _write) 188 189 190@implementer(IReactorFDSet) 191class QtReactor(posixbase.PosixReactorBase): 192 # implements(IReactorFDSet) 193 194 def __init__(self): 195 self._reads = {} 196 self._writes = {} 197 self._notifiers = {} 198 self._timer = QTimer() 199 self._timer.setSingleShot(True) 200 self._timer.timeout.connect(self.iterate_qt) 201 if QCoreApplication.instance() is None: 202 # Application Object has not been started yet 203 self.qApp = QCoreApplication([]) 204 self._ownApp = True 205 else: 206 self.qApp = QCoreApplication.instance() 207 self._ownApp = False 208 self._blockApp = None 209 posixbase.PosixReactorBase.__init__(self) 210 211 def _add(self, xer, primary, type): 212 """ 213 Private method for adding a descriptor from the event loop. 214 215 It takes care of adding it if new or modifying it if already added 216 for another state (read -> read/write for example). 217 """ 218 if xer not in primary: 219 primary[xer] = TwistedSocketNotifier(None, self, xer, type) 220 221 def addReader(self, reader): 222 """Add a FileDescriptor for notification of data available to read.""" 223 self._add(reader, self._reads, QSocketNotifier.Read) 224 225 def addWriter(self, writer): 226 """Add a FileDescriptor for notification of data available to write.""" 227 self._add(writer, self._writes, QSocketNotifier.Write) 228 229 def _remove(self, xer, primary): 230 """ 231 Private method for removing a descriptor from the event loop. 232 233 It does the inverse job of _add, and also add a check in case of the fd 234 has gone away. 235 """ 236 if xer in primary: 237 notifier = primary.pop(xer) 238 notifier.shutdown() 239 240 def removeReader(self, reader): 241 """Remove a Selectable for notification of data available to read.""" 242 self._remove(reader, self._reads) 243 244 def removeWriter(self, writer): 245 """Remove a Selectable for notification of data available to write.""" 246 self._remove(writer, self._writes) 247 248 def removeAll(self): 249 """Remove all selectables, and return a list of them.""" 250 return self._removeAll(self._reads, self._writes) 251 252 def getReaders(self): 253 return self._reads.keys() 254 255 def getWriters(self): 256 return self._writes.keys() 257 258 def callLater(self, howlong, *args, **kargs): 259 rval = super(QtReactor, self).callLater(howlong, *args, **kargs) 260 self.reactorInvocation() 261 return rval 262 263 def reactorInvocation(self): 264 self._timer.stop() 265 self._timer.setInterval(0) 266 self._timer.start() 267 268 def _iterate(self, delay=None, fromqt=False): 269 """See twisted.internet.interfaces.IReactorCore.iterate.""" 270 self.runUntilCurrent() 271 self.doIteration(delay, fromqt=fromqt) 272 273 iterate = _iterate 274 275 def iterate_qt(self, delay=None): 276 self.iterate(delay=delay, fromqt=True) 277 278 def doIteration(self, delay=None, fromqt=False): 279 """This method is called by a Qt timer or by network activity on a file descriptor""" 280 if not self.running and self._blockApp: 281 self._blockApp.quit() 282 self._timer.stop() 283 if delay is None: 284 delay = 0 285 delay = max(delay, 1) 286 if not fromqt: 287 self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000) 288 timeout = self.timeout() 289 if timeout is not None: 290 self._timer.setInterval(timeout * 1000) 291 self._timer.start() 292 293 def runReturn(self, installSignalHandlers=True): 294 self.startRunning(installSignalHandlers=installSignalHandlers) 295 self.reactorInvocation() 296 297 def run(self, installSignalHandlers=True): 298 if self._ownApp: 299 self._blockApp = self.qApp 300 else: 301 self._blockApp = QEventLoop() 302 self.runReturn() 303 self._blockApp.exec_() 304 if self.running: 305 self.stop() 306 self.runUntilCurrent() 307 308 # def sigInt(self, *args): 309 # print('I received a sigint. BAIBAI') 310 # posixbase.PosixReactorBase.sigInt() 311 # 312 # def sigTerm(self, *args): 313 # print('I received a sigterm. BAIBAI') 314 # posixbase.PosixReactorBase.sigTerm() 315 # 316 # def sigBreak(self, *args): 317 # print('I received a sigbreak. BAIBAI') 318 # posixbase.PosixReactorBase.sigBreak() 319 320 321class QtEventReactor(QtReactor): 322 def __init__(self, *args, **kwargs): 323 self._events = {} 324 super(QtEventReactor, self).__init__() 325 326 def addEvent(self, event, fd, action): 327 """Add a new win32 event to the event loop.""" 328 self._events[event] = (fd, action) 329 330 def removeEvent(self, event): 331 """Remove an event.""" 332 if event in self._events: 333 del self._events[event] 334 335 def doEvents(self): 336 handles = self._events.keys() 337 if len(handles) > 0: 338 val = None 339 while val != WAIT_TIMEOUT: 340 val = MsgWaitForMultipleObjects(handles, 0, 0, QS_ALLINPUT | QS_ALLEVENTS) 341 if val >= WAIT_OBJECT_0 and val < WAIT_OBJECT_0 + len(handles): 342 event_id = handles[val - WAIT_OBJECT_0] 343 if event_id in self._events: 344 fd, action = self._events[event_id] 345 log.callWithLogger(fd, self._runAction, action, fd) 346 elif val == WAIT_TIMEOUT: 347 pass 348 else: 349 #print 'Got an unexpected return of %r' % val 350 return 351 352 def _runAction(self, action, fd): 353 try: 354 closed = getattr(fd, action)() 355 except: 356 closed = sys.exc_info()[1] 357 log.deferr() 358 if closed: 359 self._disconnectSelectable(fd, closed, action == 'doRead') 360 361 def iterate(self, delay=None, fromqt=False): 362 """See twisted.internet.interfaces.IReactorCore.iterate.""" 363 self.runUntilCurrent() 364 self.doEvents() 365 self.doIteration(delay, fromqt=fromqt) 366 367 368def posixinstall(): 369 """Install the Qt reactor.""" 370 from twisted.internet.main import installReactor 371 p = QtReactor() 372 installReactor(p) 373 374 375def win32install(): 376 """Install the Qt reactor.""" 377 from twisted.internet.main import installReactor 378 p = QtEventReactor() 379 installReactor(p) 380 381 382if runtime.platform.getType() == 'win32': 383 from win32event import CreateEvent, MsgWaitForMultipleObjects 384 from win32event import WAIT_OBJECT_0, WAIT_TIMEOUT, QS_ALLINPUT, QS_ALLEVENTS 385 install = win32install 386else: 387 install = posixinstall 388 389 390__all__ = ["install"] 391