// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qobjectconnect.h" #include "pysideqobject.h" #include "pysideqslotobject_p.h" #include "pysidesignal.h" #include "pysideutils.h" #include "signalmanager.h" #include "shiboken.h" #include "basewrapper.h" #include "autodecref.h" #include #include #include #include #include static bool isMethodDecorator(PyObject *method, bool is_pymethod, PyObject *self) { Shiboken::AutoDecRef methodName(PyObject_GetAttr(method, Shiboken::PyMagicName::name())); if (!PyObject_HasAttr(self, methodName)) return true; Shiboken::AutoDecRef otherMethod(PyObject_GetAttr(self, methodName)); // PYSIDE-1523: Each could be a compiled method or a normal method here, for the // compiled ones we can use the attributes. PyObject *function1; if (PyMethod_Check(otherMethod.object())) { function1 = PyMethod_GET_FUNCTION(otherMethod.object()); } else { function1 = PyObject_GetAttr(otherMethod.object(), Shiboken::PyName::im_func()); if (function1 == nullptr) return false; Py_DECREF(function1); // Not retaining a reference in line with what PyMethod_GET_FUNCTION does. } PyObject *function2; if (is_pymethod) { function2 = PyMethod_GET_FUNCTION(method); } else { function2 = PyObject_GetAttr(method, Shiboken::PyName::im_func()); Py_DECREF(function2); // Not retaining a reference in line with what PyMethod_GET_FUNCTION does. } return function1 != function2; } struct GetReceiverResult { QObject *receiver = nullptr; PyObject *self = nullptr; QByteArray callbackSig; bool usingGlobalReceiver = false; int slotIndex = -1; }; #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug d, const GetReceiverResult &r) { QDebugStateSaver saver(d); d.noquote(); d.nospace(); d << "GetReceiverResult(receiver=" << r.receiver << ", self=" << r.self << ", sig=\"" << r.callbackSig << "\", slotIndex=" << r.slotIndex << ", usingGlobalReceiver=" << r.usingGlobalReceiver << ')'; return d; } #endif // QT_NO_DEBUG_STREAM static const char *getQualifiedName(PyObject *ob) { Shiboken::AutoDecRef qualNameP(PyObject_GetAttr(ob, Shiboken::PyMagicName::qualname())); return qualNameP.isNull() ? nullptr : Shiboken::String::toCString(qualNameP.object()); } // Determine whether a method is declared in a class using qualified name lookup. static bool isDeclaredIn(PyObject *method, const char *className) { bool result = false; if (auto *qualifiedNameC = getQualifiedName(PyMethod_Function(method))) { std::string_view qualifiedName(qualifiedNameC); if (const auto dot = qualifiedName.rfind('.'); dot != std::string::npos) result = qualifiedName.substr(0, dot) == className; } return result; } static GetReceiverResult getReceiver(QObject *source, const char *signal, PyObject *callback) { GetReceiverResult result; bool forceGlobalReceiver = false; if (PyMethod_Check(callback)) { result.self = PyMethod_GET_SELF(callback); result.receiver = PySide::convertToQObject(result.self, false); forceGlobalReceiver = isMethodDecorator(callback, true, result.self); #ifdef PYPY_VERSION } else if (Py_TYPE(callback) == PepBuiltinMethod_TypePtr) { result.self = PyObject_GetAttrString(callback, "__self__"); Py_DECREF(result.self); result.receiver = PySide::convertToQObject(result.self, false); #endif } else if (PyCFunction_Check(callback)) { result.self = PyCFunction_GET_SELF(callback); result.receiver = PySide::convertToQObject(result.self, false); } else if (PySide::isCompiledMethod(callback)) { result.self = PyObject_GetAttr(callback, Shiboken::PyName::im_self()); Py_DECREF(result.self); result.receiver = PySide::convertToQObject(result.self, false); forceGlobalReceiver = isMethodDecorator(callback, false, result.self); } else if (PyCallable_Check(callback)) { // Ok, just a callable object result.receiver = nullptr; result.self = nullptr; } result.usingGlobalReceiver = !result.receiver || forceGlobalReceiver; // Check if this callback is a overwrite of a non-virtual Qt slot (pre-Jira bug 1019). // Make it possible to connect to a MyWidget.show() although QWidget.show() // is a non-virtual slot which would be found by QMetaObject search. // FIXME PYSIDE7: This is arguably a bit of a misguided "feature", remove? if (!result.usingGlobalReceiver && result.receiver && result.self) { result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); if (PyMethod_Check(callback) != 0 && result.slotIndex != -1 && result.slotIndex < metaObject->methodOffset()) { // Find the class in which the slot is declared. while (result.slotIndex < metaObject->methodOffset()) metaObject = metaObject->superClass(); // If the Python callback is not declared in the same class, assume it is // a Python override. Resort to global receiver (PYSIDE-2418). if (!isDeclaredIn(callback, metaObject->className())) result.usingGlobalReceiver = true; } } const auto receiverThread = result.receiver ? result.receiver->thread() : nullptr; if (result.usingGlobalReceiver) { PySide::SignalManager &signalManager = PySide::SignalManager::instance(); result.receiver = signalManager.globalReceiver(source, callback, result.receiver); // PYSIDE-1354: Move the global receiver to the original receivers's thread // so that autoconnections work correctly. if (receiverThread && receiverThread != result.receiver->thread()) result.receiver->moveToThread(receiverThread); result.callbackSig = PySide::Signal::getCallbackSignature(signal, result.receiver, callback, result.usingGlobalReceiver); const QMetaObject *metaObject = result.receiver->metaObject(); result.slotIndex = metaObject->indexOfSlot(result.callbackSig.constData()); } return result; } namespace PySide { class FriendlyQObject : public QObject // Make protected connectNotify() accessible. { public: using QObject::connectNotify; using QObject::disconnectNotify; }; QMetaObject::Connection qobjectConnect(QObject *source, const char *signal, QObject *receiver, const char *slot, Qt::ConnectionType type) { if (!signal || !slot || !PySide::Signal::checkQtSignal(signal)) return {}; if (!PySide::SignalManager::registerMetaMethod(source, signal + 1, QMetaMethod::Signal)) return {}; const auto methodType = PySide::Signal::isQtSignal(slot) ? QMetaMethod::Signal : QMetaMethod::Slot; PySide::SignalManager::registerMetaMethod(receiver, slot + 1, methodType); return QObject::connect(source, signal, receiver, slot, type); } QMetaObject::Connection qobjectConnect(QObject *source, QMetaMethod signal, QObject *receiver, QMetaMethod slot, Qt::ConnectionType type) { return qobjectConnect(source, signal.methodSignature().constData(), receiver, slot.methodSignature().constData(), type); } QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, PyObject *callback, Qt::ConnectionType type) { if (!signal || !PySide::Signal::checkQtSignal(signal)) return {}; const int signalIndex = PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, QMetaMethod::Signal); if (signalIndex == -1) return {}; // Extract receiver from callback const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); if (receiver.receiver == nullptr && receiver.self == nullptr) return {}; int slotIndex = receiver.slotIndex; PySide::SignalManager &signalManager = PySide::SignalManager::instance(); if (slotIndex == -1) { if (!receiver.usingGlobalReceiver && receiver.self && !Shiboken::Object::hasCppWrapper(reinterpret_cast(receiver.self))) { qWarning("You can't add dynamic slots on an object originated from C++."); if (receiver.usingGlobalReceiver) signalManager.releaseGlobalReceiver(source, receiver.receiver); return {}; } const char *slotSignature = receiver.callbackSig.constData(); slotIndex = receiver.usingGlobalReceiver ? signalManager.globalReceiverSlotIndex(receiver.receiver, slotSignature) : PySide::SignalManager::registerMetaMethodGetIndex(receiver.receiver, slotSignature, QMetaMethod::Slot); if (slotIndex == -1) { if (receiver.usingGlobalReceiver) signalManager.releaseGlobalReceiver(source, receiver.receiver); return {}; } } QMetaObject::Connection connection{}; Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() connection = QMetaObject::connect(source, signalIndex, receiver.receiver, slotIndex, type); Py_END_ALLOW_THREADS if (!connection) { if (receiver.usingGlobalReceiver) signalManager.releaseGlobalReceiver(source, receiver.receiver); return {}; } Q_ASSERT(receiver.receiver); if (receiver.usingGlobalReceiver) signalManager.notifyGlobalReceiver(receiver.receiver); const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); static_cast(source)->connectNotify(signalMethod); return connection; } QMetaObject::Connection qobjectConnectCallback(QObject *source, const char *signal, QObject *context, PyObject *callback, Qt::ConnectionType type) { if (!signal || !PySide::Signal::checkQtSignal(signal)) return {}; const int signalIndex = PySide::SignalManager::registerMetaMethodGetIndex(source, signal + 1, QMetaMethod::Signal); if (signalIndex == -1) return {}; // Extract receiver from callback const GetReceiverResult receiver = getReceiver(source, signal + 1, callback); if (receiver.receiver == nullptr && receiver.self == nullptr) return {}; PySide::SignalManager &signalManager = PySide::SignalManager::instance(); PySideQSlotObject *slotObject = new PySideQSlotObject(callback); QMetaObject::Connection connection{}; Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with connectNotify() connection = QObjectPrivate::connect(source, signalIndex, context, slotObject, type); Py_END_ALLOW_THREADS if (!connection) { if (receiver.usingGlobalReceiver) signalManager.releaseGlobalReceiver(source, receiver.receiver); return {}; } Q_ASSERT(receiver.receiver); if (receiver.usingGlobalReceiver) signalManager.notifyGlobalReceiver(receiver.receiver); const QMetaMethod signalMethod = receiver.receiver->metaObject()->method(signalIndex); static_cast(source)->connectNotify(signalMethod); return connection; } bool qobjectDisconnectCallback(QObject *source, const char *signal, PyObject *callback) { if (!PySide::Signal::checkQtSignal(signal)) return false; // Extract receiver from callback const GetReceiverResult receiver = getReceiver(nullptr, signal, callback); if (receiver.receiver == nullptr && receiver.self == nullptr) return false; const int signalIndex = source->metaObject()->indexOfSignal(signal + 1); const int slotIndex = receiver.slotIndex; bool ok{}; Py_BEGIN_ALLOW_THREADS // PYSIDE-2367, prevent threading deadlocks with disconnectNotify() ok = QMetaObject::disconnectOne(source, signalIndex, receiver.receiver, slotIndex); Py_END_ALLOW_THREADS if (!ok) return false; Q_ASSERT(receiver.receiver); const QMetaMethod slotMethod = receiver.receiver->metaObject()->method(slotIndex); static_cast(source)->disconnectNotify(slotMethod); if (receiver.usingGlobalReceiver) { // might delete the receiver PySide::SignalManager &signalManager = PySide::SignalManager::instance(); signalManager.releaseGlobalReceiver(source, receiver.receiver); } return true; } } // namespace PySide