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"]