1 // This contains the implementation of the PyQtSlotProxy class.
2 //
3 // Copyright (c) 2021 Riverbank Computing Limited <info@riverbankcomputing.com>
4 //
5 // This file is part of PyQt5.
6 //
7 // This file may be used under the terms of the GNU General Public License
8 // version 3.0 as published by the Free Software Foundation and appearing in
9 // the file LICENSE included in the packaging of this file. Please review the
10 // following information to ensure the GNU General Public License version 3.0
11 // requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 //
13 // If you do not wish to use this file under the terms of the GPL version 3.0
14 // then you may purchase a commercial license. For more information contact
15 // info@riverbankcomputing.com.
16 //
17 // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
18 // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
19
20
21 #include <Python.h>
22
23 #include <QByteArray>
24 #include <QMetaObject>
25 #include <QMutex>
26 #include <QObject>
27
28 #include "qpycore_api.h"
29 #include "qpycore_chimera.h"
30 #include "qpycore_qmetaobjectbuilder.h"
31 #include "qpycore_pyqtslot.h"
32 #include "qpycore_pyqtslotproxy.h"
33
34 #include "sipAPIQtCore.h"
35
36
37 // Proxy flags.
38 #define PROXY_SINGLE_SHOT 0x01 // The slot will only be called once.
39 #define PROXY_SLOT_INVOKED 0x02 // The proxied slot is executing.
40 #define PROXY_SLOT_DISABLED 0x04 // The proxy deleteLater() has been called
41 // or should be called after the slot has
42 // finished executing.
43 #define PROXY_NO_RCVR_CHECK 0x08 // The existence of the receiver C++ object
44 // should not be checked.
45
46
47 // The last QObject sender.
48 QObject *PyQtSlotProxy::last_sender = 0;
49
50
51 // Create a universal proxy used as a slot. Note that this will leak if there
52 // is no transmitter and this is not a single shot slot. There will be no
53 // meta-object if there was a problem creating the proxy. This is called
54 // without the GIL.
PyQtSlotProxy(PyObject * slot,QObject * q_tx,const Chimera::Signature * slot_signature,bool single_shot)55 PyQtSlotProxy::PyQtSlotProxy(PyObject *slot, QObject *q_tx,
56 const Chimera::Signature *slot_signature, bool single_shot)
57 : QObject(), proxy_flags(single_shot ? PROXY_SINGLE_SHOT : 0),
58 signature(slot_signature->signature), transmitter(q_tx)
59 {
60 SIP_BLOCK_THREADS
61 real_slot = new PyQtSlot(slot, slot_signature);
62 SIP_UNBLOCK_THREADS
63
64 // Create a new meta-object on the heap so that it looks like it has a slot
65 // of the right name and signature.
66 QMetaObjectBuilder builder;
67
68 builder.setClassName("PyQtSlotProxy");
69 builder.setSuperClass(&QObject::staticMetaObject);
70
71 builder.addSlot("unislot()");
72 builder.addSlot("disable()");
73
74 meta_object = builder.toMetaObject();
75
76 // Detect when any transmitter is destroyed. (Note that we used to do this
77 // by making the proxy a child of the transmitter. This doesn't work as
78 // expected because QWidget destroys its children before emitting the
79 // destroyed signal.) We use a queued connection in case the proxy is also
80 // connected to the same signal and we want to make sure it has a chance to
81 // invoke the slot before being destroyed.
82 if (transmitter)
83 {
84 // Add this one to the global hash.
85 mutex->lock();
86 proxy_slots.insert(transmitter, this);
87 mutex->unlock();
88
89 connect(transmitter, SIGNAL(destroyed(QObject *)), SLOT(disable()),
90 Qt::QueuedConnection);
91 }
92 }
93
94
95 // Destroy a universal proxy. This is called without the GIL.
~PyQtSlotProxy()96 PyQtSlotProxy::~PyQtSlotProxy()
97 {
98 Q_ASSERT((proxy_flags & PROXY_SLOT_INVOKED) == 0);
99
100 if (transmitter)
101 {
102 mutex->lock();
103
104 ProxyHash::iterator it(proxy_slots.find(transmitter));
105 ProxyHash::iterator end(proxy_slots.end());
106
107 while (it != end && it.key() == transmitter)
108 {
109 if (it.value() == this)
110 it = proxy_slots.erase(it);
111 else
112 ++it;
113 }
114
115 mutex->unlock();
116 }
117
118 // Qt can still be tidying up after Python has gone so make sure that it
119 // hasn't.
120 if (Py_IsInitialized())
121 {
122 SIP_BLOCK_THREADS
123 delete real_slot;
124 SIP_UNBLOCK_THREADS
125 }
126
127 if (meta_object)
128 {
129 free(const_cast<QMetaObject *>(meta_object));
130 }
131 }
132
133
134 // The static members of PyQtSlotProxy.
135 const QByteArray PyQtSlotProxy::proxy_slot_signature(SLOT(unislot()));
136 QMutex *PyQtSlotProxy::mutex;
137 PyQtSlotProxy::ProxyHash PyQtSlotProxy::proxy_slots;
138
139
metaObject() const140 const QMetaObject *PyQtSlotProxy::metaObject() const
141 {
142 return meta_object;
143 }
144
145
qt_metacast(const char * _clname)146 void *PyQtSlotProxy::qt_metacast(const char *_clname)
147 {
148 if (!_clname)
149 return 0;
150
151 if (qstrcmp(_clname, "PyQtSlotProxy") == 0)
152 return static_cast<void *>(const_cast<PyQtSlotProxy *>(this));
153
154 return QObject::qt_metacast(_clname);
155 }
156
157
qt_metacall(QMetaObject::Call _c,int _id,void ** _a)158 int PyQtSlotProxy::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
159 {
160 _id = QObject::qt_metacall(_c, _id, _a);
161
162 if (_id < 0)
163 return _id;
164
165 if (_c == QMetaObject::InvokeMetaMethod)
166 {
167 switch (_id)
168 {
169 case 0:
170 unislot(_a);
171 break;
172
173 case 1:
174 disable();
175 break;
176 }
177
178 _id -= 2;
179 }
180
181 return _id;
182 }
183
184
185 // This is the universal slot itself that dispatches to the real slot.
unislot(void ** qargs)186 void PyQtSlotProxy::unislot(void **qargs)
187 {
188 // If we are marked as disabled (possible if a queued signal has been
189 // disconnected but there is still a signal in the event queue) then just
190 // ignore the call.
191 if (proxy_flags & PROXY_SLOT_DISABLED)
192 return;
193
194 // sender() must be called without the GIL to avoid possible deadlocks
195 // between the GIL and Qt's internal thread data mutex.
196 QObject *new_last_sender = sender();
197
198 SIP_BLOCK_THREADS
199
200 QObject *saved_last_sender = last_sender;
201 last_sender = new_last_sender;
202
203 proxy_flags |= PROXY_SLOT_INVOKED;
204
205 switch (real_slot->invoke(qargs, (proxy_flags & PROXY_NO_RCVR_CHECK)))
206 {
207 case PyQtSlot::Succeeded:
208 break;
209
210 case PyQtSlot::Failed:
211 pyqt5_err_print();
212 break;
213
214 case PyQtSlot::Ignored:
215 proxy_flags |= PROXY_SLOT_DISABLED;
216 break;
217 }
218
219 proxy_flags &= ~PROXY_SLOT_INVOKED;
220
221 // Self destruct if we are a single shot or disabled.
222 if (proxy_flags & (PROXY_SINGLE_SHOT|PROXY_SLOT_DISABLED))
223 {
224 // See the comment in disable() for why we deleteLater().
225 deleteLater();
226 }
227
228 last_sender = saved_last_sender;
229
230 SIP_UNBLOCK_THREADS
231 }
232
233
234 // Disable the slot by destroying it if possible, or delaying its destruction
235 // until the proxied slot returns.
disable()236 void PyQtSlotProxy::disable()
237 {
238 proxy_flags |= PROXY_SLOT_DISABLED;
239
240 // Delete it if the slot isn't currently executing, otherwise it will be
241 // done after the slot returns. Note that we don't rely on deleteLater()
242 // providing the necessary delay because the slot could process the event
243 // loop and the proxy would be deleted too soon.
244 if ((proxy_flags & PROXY_SLOT_INVOKED) == 0)
245 {
246 // Despite what the Qt documentation suggests, if there are outstanding
247 // queued signals then we may crash so we always delete later when the
248 // event queue will have been flushed.
249 deleteLater();
250 }
251 }
252
253
254 // Find a slot proxy connected to a transmitter.
findSlotProxy(const QObject * transmitter,const QByteArray & signal_signature,PyObject * slot)255 PyQtSlotProxy *PyQtSlotProxy::findSlotProxy(const QObject *transmitter,
256 const QByteArray &signal_signature, PyObject *slot)
257 {
258 PyQtSlotProxy *proxy = 0;
259
260 mutex->lock();
261
262 ProxyHash::const_iterator it(proxy_slots.find(transmitter));
263 ProxyHash::const_iterator end(proxy_slots.end());
264
265 while (it != end && it.key() == transmitter)
266 {
267 PyQtSlotProxy *sp = it.value();
268
269 if (!(sp->proxy_flags & PROXY_SLOT_DISABLED) && sp->signature == signal_signature && *(sp->real_slot) == slot)
270 {
271 proxy = sp;
272 break;
273 }
274
275 ++it;
276 }
277
278 mutex->unlock();
279
280 return proxy;
281 }
282
283
284 // Delete any slot proxy for a particular connection.
deleteSlotProxy(const QMetaObject::Connection * connection)285 void PyQtSlotProxy::deleteSlotProxy(const QMetaObject::Connection *connection)
286 {
287 mutex->lock();
288
289 ProxyHash::iterator it(proxy_slots.begin());
290 ProxyHash::iterator end(proxy_slots.end());
291
292 while (it != end)
293 {
294 PyQtSlotProxy *sp = it.value();
295
296 if (sp->connection == *connection)
297 {
298 proxy_slots.erase(it);
299 sp->disable();
300
301 break;
302 }
303
304 ++it;
305 }
306
307 mutex->unlock();
308 }
309
310
311 // Delete any slot proxies for a particular transmitter and optional signal
312 // signature.
deleteSlotProxies(const QObject * transmitter,const QByteArray & signal_signature)313 void PyQtSlotProxy::deleteSlotProxies(const QObject *transmitter,
314 const QByteArray &signal_signature)
315 {
316 mutex->lock();
317
318 ProxyHash::iterator it(proxy_slots.find(transmitter));
319 ProxyHash::iterator end(proxy_slots.end());
320
321 while (it != end && it.key() == transmitter)
322 {
323 PyQtSlotProxy *sp = it.value();
324
325 if (signal_signature.isEmpty() || signal_signature == sp->signature)
326 {
327 it = proxy_slots.erase(it);
328 sp->disable();
329 }
330 else
331 {
332 ++it;
333 }
334 }
335
336 mutex->unlock();
337 }
338
339
340 // Clear the extra references of any slots connected to a transmitter. This is
341 // called with the GIL.
clearSlotProxies(const QObject * transmitter)342 int PyQtSlotProxy::clearSlotProxies(const QObject *transmitter)
343 {
344 ProxyHash::iterator it(proxy_slots.find(transmitter));
345 ProxyHash::iterator end(proxy_slots.end());
346
347 while (it != end && it.key() == transmitter)
348 {
349 it.value()->real_slot->clearOther();
350
351 ++it;
352 }
353
354 return 0;
355 }
356
357
358 // A thing wrapper available to the generated code.
qpycore_clearSlotProxies(const QObject * transmitter)359 int qpycore_clearSlotProxies(const QObject *transmitter)
360 {
361 return PyQtSlotProxy::clearSlotProxies(transmitter);
362 }
363
364
365 // Visit the extra references of any slots connected to a transmitter. This is
366 // called with the GIL.
visitSlotProxies(const QObject * transmitter,visitproc visit,void * arg)367 int PyQtSlotProxy::visitSlotProxies(const QObject *transmitter,
368 visitproc visit, void *arg)
369 {
370 int vret = 0;
371
372 ProxyHash::iterator it(proxy_slots.find(transmitter));
373 ProxyHash::iterator end(proxy_slots.end());
374
375 while (it != end && it.key() == transmitter)
376 {
377 if ((vret = it.value()->real_slot->visitOther(visit, arg)) != 0)
378 break;
379
380 ++it;
381 }
382
383 return vret;
384 }
385
386
387 // A thing wrapper available to the generated code.
qpycore_visitSlotProxies(const QObject * transmitter,visitproc visit,void * arg)388 int qpycore_visitSlotProxies(const QObject *transmitter, visitproc visit,
389 void *arg)
390 {
391 return PyQtSlotProxy::visitSlotProxies(transmitter, visit, arg);
392 }
393
394
395 // Get the last sender.
lastSender()396 QObject *PyQtSlotProxy::lastSender()
397 {
398 return last_sender;
399 }
400
401
402 // Disable the check that the receiver C++ object exists before invoking a
403 // slot.
disableReceiverCheck()404 void PyQtSlotProxy::disableReceiverCheck()
405 {
406 proxy_flags |= PROXY_NO_RCVR_CHECK;
407 }
408