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