1 | # Copyright (c) 2001-2004 Twisted Matrix Laboratories. |
---|
2 | # See LICENSE for details. |
---|
3 | |
---|
4 | |
---|
5 | """ |
---|
6 | This module provides support for Twisted to interact with the PyQt mainloop. |
---|
7 | |
---|
8 | In order to use this support, simply do the following:: |
---|
9 | |
---|
10 | | from twisted.internet import qtreactor |
---|
11 | | qtreactor.install() |
---|
12 | |
---|
13 | Then use twisted.internet APIs as usual. The other methods here are not |
---|
14 | intended to be called directly. |
---|
15 | |
---|
16 | API Stability: stable |
---|
17 | |
---|
18 | Maintainer: U{Itamar Shtull-Trauring<mailto:twisted@itamarst.org>} |
---|
19 | Port to QT4: U{Gabe Rudy<mailto:rudy@goldenhelix.com>} |
---|
20 | """ |
---|
21 | |
---|
22 | __all__ = ['install'] |
---|
23 | |
---|
24 | # System Imports |
---|
25 | from PyQt4.QtCore import QSocketNotifier, QObject, SIGNAL, QTimer |
---|
26 | from PyQt4.QtGui import QApplication |
---|
27 | import sys |
---|
28 | |
---|
29 | # Twisted Imports |
---|
30 | from twisted.python import log, failure |
---|
31 | from twisted.internet import posixbase |
---|
32 | |
---|
33 | reads = {} |
---|
34 | writes = {} |
---|
35 | hasReader = reads.has_key |
---|
36 | hasWriter = writes.has_key |
---|
37 | |
---|
38 | |
---|
39 | class TwistedSocketNotifier(QSocketNotifier): |
---|
40 | '''Connection between an fd event and reader/writer callbacks''' |
---|
41 | |
---|
42 | def __init__(self, reactor, watcher, type): |
---|
43 | QSocketNotifier.__init__(self, watcher.fileno(), type) |
---|
44 | self.reactor = reactor |
---|
45 | self.watcher = watcher |
---|
46 | self.fn = None |
---|
47 | if type == QSocketNotifier.Read: |
---|
48 | self.fn = self.read |
---|
49 | elif type == QSocketNotifier.Write: |
---|
50 | self.fn = self.write |
---|
51 | QObject.connect(self, SIGNAL("activated(int)"), self.fn) |
---|
52 | |
---|
53 | def shutdown(self): |
---|
54 | QObject.disconnect(self, SIGNAL("activated(int)"), self.fn) |
---|
55 | self.setEnabled(0) |
---|
56 | self.fn = self.watcher = None |
---|
57 | |
---|
58 | def read(self, sock): |
---|
59 | why = None |
---|
60 | w = self.watcher |
---|
61 | try: |
---|
62 | why = w.doRead() |
---|
63 | except: |
---|
64 | why = sys.exc_info()[1] |
---|
65 | log.msg('Error in %s.doRead()' % w) |
---|
66 | log.deferr() |
---|
67 | if why: |
---|
68 | self.reactor._disconnectSelectable(w, why, True) |
---|
69 | self.reactor.simulate() |
---|
70 | |
---|
71 | def write(self, sock): |
---|
72 | why = None |
---|
73 | w = self.watcher |
---|
74 | self.setEnabled(0) |
---|
75 | try: |
---|
76 | why = w.doWrite() |
---|
77 | except: |
---|
78 | why = sys.exc_value |
---|
79 | log.msg('Error in %s.doWrite()' % w) |
---|
80 | log.deferr() |
---|
81 | if why: |
---|
82 | self.reactor.removeReader(w) |
---|
83 | self.reactor.removeWriter(w) |
---|
84 | try: |
---|
85 | w.connectionLost(failure.Failure(why)) |
---|
86 | except: |
---|
87 | log.deferr() |
---|
88 | elif self.watcher: |
---|
89 | self.setEnabled(1) |
---|
90 | self.reactor.simulate() |
---|
91 | |
---|
92 | |
---|
93 | class QTReactor(posixbase.PosixReactorBase): |
---|
94 | """Qt based reactor.""" |
---|
95 | |
---|
96 | # Reference to a DelayedCall for self.crash() when the reactor is |
---|
97 | # entered through .iterate() |
---|
98 | _crashCall = None |
---|
99 | |
---|
100 | _timer = None |
---|
101 | |
---|
102 | def __init__(self, app=None): |
---|
103 | self.running = 0 |
---|
104 | posixbase.PosixReactorBase.__init__(self) |
---|
105 | if app is None: |
---|
106 | app = QApplication([]) |
---|
107 | self.qApp = app |
---|
108 | self.addSystemEventTrigger('after', 'shutdown', self.cleanup) |
---|
109 | |
---|
110 | def addReader(self, reader): |
---|
111 | if not hasReader(reader): |
---|
112 | reads[reader] = TwistedSocketNotifier(self, reader, QSocketNotifier.Read) |
---|
113 | |
---|
114 | def addWriter(self, writer): |
---|
115 | if not hasWriter(writer): |
---|
116 | writes[writer] = TwistedSocketNotifier(self, writer, QSocketNotifier.Write) |
---|
117 | |
---|
118 | def removeReader(self, reader): |
---|
119 | if hasReader(reader): |
---|
120 | reads[reader].shutdown() |
---|
121 | del reads[reader] |
---|
122 | |
---|
123 | def removeWriter(self, writer): |
---|
124 | if hasWriter(writer): |
---|
125 | writes[writer].shutdown() |
---|
126 | del writes[writer] |
---|
127 | |
---|
128 | def removeAll(self): |
---|
129 | return self._removeAll(reads, writes) |
---|
130 | |
---|
131 | def simulate(self): |
---|
132 | if self._timer is not None: |
---|
133 | self._timer.stop() |
---|
134 | self._timer = None |
---|
135 | |
---|
136 | if not self.running: |
---|
137 | self.running = 1 |
---|
138 | self.qApp.exit_loop() |
---|
139 | return |
---|
140 | self.runUntilCurrent() |
---|
141 | |
---|
142 | if self._crashCall is not None: |
---|
143 | self._crashCall.reset(0) |
---|
144 | |
---|
145 | # gah |
---|
146 | timeout = self.timeout() |
---|
147 | if timeout is None: |
---|
148 | timeout = 1.0 |
---|
149 | timeout = min(timeout, 0.1) * 1010 |
---|
150 | |
---|
151 | if self._timer is None: |
---|
152 | self._timer = QTimer() |
---|
153 | QObject.connect(self._timer, SIGNAL("timeout()"), self.simulate) |
---|
154 | self._timer.start(timeout) |
---|
155 | |
---|
156 | def cleanup(self): |
---|
157 | if self._timer is not None: |
---|
158 | self._timer.stop() |
---|
159 | self._timer = None |
---|
160 | |
---|
161 | def iterate(self, delay=0.0): |
---|
162 | log.msg(channel='system', event='iteration', reactor=self) |
---|
163 | self._crashCall = self.callLater(delay, self.crash) |
---|
164 | self.run() |
---|
165 | |
---|
166 | def run(self, installSignalHandlers=1): |
---|
167 | self.running = 1 |
---|
168 | self.startRunning(installSignalHandlers=installSignalHandlers) |
---|
169 | self.simulate() |
---|
170 | self.qApp.exec_() |
---|
171 | |
---|
172 | def crash(self): |
---|
173 | if self._crashCall is not None: |
---|
174 | if self._crashCall.active(): |
---|
175 | self._crashCall.cancel() |
---|
176 | self._crashCall = None |
---|
177 | self.running = 0 |
---|
178 | |
---|
179 | |
---|
180 | def install(app=None): |
---|
181 | """Configure the twisted mainloop to be run inside the qt mainloop. |
---|
182 | """ |
---|
183 | from twisted.internet import main |
---|
184 | |
---|
185 | reactor = QTReactor(app=app) |
---|
186 | main.installReactor(reactor) |
---|