aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpyside/globalreceiverv2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpyside/globalreceiverv2.cpp')
-rw-r--r--sources/pyside6/libpyside/globalreceiverv2.cpp323
1 files changed, 323 insertions, 0 deletions
diff --git a/sources/pyside6/libpyside/globalreceiverv2.cpp b/sources/pyside6/libpyside/globalreceiverv2.cpp
new file mode 100644
index 000000000..51070b4ad
--- /dev/null
+++ b/sources/pyside6/libpyside/globalreceiverv2.cpp
@@ -0,0 +1,323 @@
+// Copyright (C) 2016 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 "globalreceiverv2.h"
+#include "pysideweakref.h"
+#include "pysidestaticstrings.h"
+#include "pysideutils.h"
+#include "signalmanager.h"
+
+#include <autodecref.h>
+#include <gilstate.h>
+#include <pep384ext.h>
+
+#include <QtCore/QMetaMethod>
+#include <QtCore/QSet>
+#include <QtCore/QDebug>
+
+#include <cstring>
+
+#define RECEIVER_DESTROYED_SLOT_NAME "__receiverDestroyed__(QObject*)"
+
+
+namespace PySide
+{
+
+class DynamicSlotDataV2
+{
+ Q_DISABLE_COPY_MOVE(DynamicSlotDataV2)
+ public:
+ DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent);
+ ~DynamicSlotDataV2();
+
+ int addSlot(const char *signature);
+ int id(const char *signature) const;
+ PyObject *callback();
+ GlobalReceiverKey key() const { return {m_pythonSelf, m_callback}; }
+ void notify();
+
+ static void onCallbackDestroyed(void *data);
+ static GlobalReceiverKey key(PyObject *callback);
+
+ void formatDebug(QDebug &debug) const;
+
+ private:
+ bool m_isMethod;
+ PyObject *m_callback;
+ PyObject *m_pythonSelf = nullptr;
+ PyObject *m_pyClass = nullptr;
+ PyObject *m_weakRef = nullptr;
+ QMap<QByteArray, int> m_signatures;
+ GlobalReceiverV2 *m_parent;
+};
+
+void DynamicSlotDataV2::formatDebug(QDebug &debug) const
+{
+ debug << "method=" << m_isMethod << ", m_callback=" << m_callback;
+ if (m_callback != nullptr)
+ debug << '/' << Py_TYPE(m_callback)->tp_name;
+ debug << ", self=" << m_pythonSelf;
+ if (m_pythonSelf != nullptr)
+ debug << '/' << Py_TYPE(m_pythonSelf)->tp_name;
+ debug << ", m_pyClass=" << m_pyClass;
+ if (m_pyClass != nullptr)
+ debug << '/' << Py_TYPE(m_pyClass)->tp_name;
+ debug << ", signatures=" << m_signatures.keys();
+}
+
+QDebug operator<<(QDebug debug, const DynamicSlotDataV2 *d)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "DynamicSlotDataV2(";
+ if (d)
+ d->formatDebug(debug);
+ else
+ debug << '0';
+ debug << ')';
+ return debug;
+}
+
+using namespace PySide;
+
+DynamicSlotDataV2::DynamicSlotDataV2(PyObject *callback, GlobalReceiverV2 *parent) :
+ m_parent(parent)
+{
+ Shiboken::GilState gil;
+
+ if (PyMethod_Check(callback)) {
+ m_isMethod = true;
+ // To avoid increment instance reference keep the callback information
+ m_callback = PyMethod_GET_FUNCTION(callback);
+ Py_INCREF(m_callback);
+ m_pythonSelf = PyMethod_GET_SELF(callback);
+
+ //monitor class from method lifetime
+ m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this);
+ } else if (PySide::isCompiledMethod(callback)) {
+ // PYSIDE-1523: PyMethod_Check is not accepting compiled form, we just go by attributes.
+ m_isMethod = true;
+
+ m_callback = PyObject_GetAttr(callback, PySide::PySideName::im_func());
+ Py_DECREF(m_callback);
+
+ m_pythonSelf = PyObject_GetAttr(callback, PySide::PySideName::im_self());
+ Py_DECREF(m_pythonSelf);
+
+ //monitor class from method lifetime
+ m_weakRef = WeakRef::create(m_pythonSelf, DynamicSlotDataV2::onCallbackDestroyed, this);
+ } else {
+ m_isMethod = false;
+
+ m_callback = callback;
+ Py_INCREF(m_callback);
+ }
+}
+
+GlobalReceiverKey DynamicSlotDataV2::key(PyObject *callback)
+{
+ Shiboken::GilState gil;
+ if (PyMethod_Check(callback)) {
+ // PYSIDE-1422: Avoid hash on self which might be unhashable.
+ return {PyMethod_GET_SELF(callback), PyMethod_GET_FUNCTION(callback)};
+ } else if (PySide::isCompiledMethod(callback)) {
+ // PYSIDE-1589: Fix for slots in compiled functions
+ Shiboken::AutoDecRef self(PyObject_GetAttr(callback, PySide::PySideName::im_self()));
+ Shiboken::AutoDecRef func(PyObject_GetAttr(callback, PySide::PySideName::im_func()));
+ return {self, func};
+ }
+ return {nullptr, callback};
+}
+
+PyObject *DynamicSlotDataV2::callback()
+{
+ PyObject *callback = m_callback;
+
+ //create a callback based on method data
+ if (m_isMethod)
+ callback = PepExt_Type_CallDescrGet(m_callback, m_pythonSelf, nullptr);
+ else
+ Py_INCREF(callback);
+
+ return callback;
+}
+
+int DynamicSlotDataV2::id(const char *signature) const
+{
+ const auto it = m_signatures.constFind(signature);
+ return it != m_signatures.cend() ? it.value() : -1;
+}
+
+int DynamicSlotDataV2::addSlot(const char *signature)
+{
+ int index = id(signature);
+ if (index == -1)
+ index = m_signatures[signature] = m_parent->metaObjectBuilder().addSlot(signature);
+ return index;
+}
+
+void DynamicSlotDataV2::onCallbackDestroyed(void *data)
+{
+ auto self = reinterpret_cast<DynamicSlotDataV2 *>(data);
+ self->m_weakRef = nullptr;
+ Py_BEGIN_ALLOW_THREADS
+ SignalManager::instance().deleteGlobalReceiver(self->m_parent);
+ Py_END_ALLOW_THREADS
+}
+
+DynamicSlotDataV2::~DynamicSlotDataV2()
+{
+ Shiboken::GilState gil;
+
+ Py_XDECREF(m_weakRef);
+ m_weakRef = nullptr;
+
+ Py_DECREF(m_callback);
+}
+
+const char *GlobalReceiverV2::senderDynamicProperty = "_q_pyside_sender";
+
+GlobalReceiverV2::GlobalReceiverV2(PyObject *callback, QObject *receiver) :
+ QObject(nullptr),
+ m_metaObject("__GlobalReceiver__", &QObject::staticMetaObject),
+ m_receiver(receiver)
+{
+ m_data = new DynamicSlotDataV2(callback, this);
+}
+
+GlobalReceiverV2::~GlobalReceiverV2()
+{
+ m_refs.clear();
+ // Remove itself from map.
+ // Suppress handling of destroyed() for objects whose last reference is contained inside
+ // the callback object that will now be deleted. The reference could be a default argument,
+ // a callback local variable, etc.
+ // The signal has to be suppressed because it would lead to the following situation:
+ // Callback is deleted, hence the last reference is decremented,
+ // leading to the object being deleted, which emits destroyed(), which would try to invoke
+ // the already deleted callback, and also try to delete the object again.
+ DynamicSlotDataV2 *data = m_data;
+ m_data = nullptr;
+ delete data;
+}
+
+int GlobalReceiverV2::addSlot(const char *signature)
+{
+ return m_data->addSlot(signature);
+}
+
+void GlobalReceiverV2::incRef(const QObject *link)
+{
+ Q_ASSERT(link);
+ m_refs.append(link);
+}
+
+void GlobalReceiverV2::decRef(const QObject *link)
+{
+ Q_ASSERT(link);
+ m_refs.removeOne(link);
+}
+
+void GlobalReceiverV2::notify()
+{
+ purgeDeletedSenders();
+}
+
+static bool isNull(const QPointer<const QObject> &p)
+{
+ return p.isNull();
+}
+
+void GlobalReceiverV2::purgeDeletedSenders()
+{
+ m_refs.erase(std::remove_if(m_refs.begin(), m_refs.end(), isNull), m_refs.end());
+}
+
+bool GlobalReceiverV2::isEmpty() const
+{
+ return std::all_of(m_refs.cbegin(), m_refs.cend(), isNull);
+}
+
+GlobalReceiverKey GlobalReceiverV2::key() const
+{
+ return m_data->key();
+}
+
+GlobalReceiverKey GlobalReceiverV2::key(PyObject *callback)
+{
+ return DynamicSlotDataV2::key(callback);
+}
+
+const QMetaObject *GlobalReceiverV2::metaObject() const
+{
+ return const_cast<GlobalReceiverV2 *>(this)->m_metaObject.update();
+}
+
+int GlobalReceiverV2::qt_metacall(QMetaObject::Call call, int id, void **args)
+{
+ Shiboken::GilState gil;
+ Q_ASSERT(call == QMetaObject::InvokeMetaMethod);
+ Q_ASSERT(id >= QObject::staticMetaObject.methodCount());
+
+ QMetaMethod slot = metaObject()->method(id);
+ Q_ASSERT(slot.methodType() == QMetaMethod::Slot);
+
+ if (!m_data) {
+ const QByteArray message = "PySide6 Warning: Skipping callback call "
+ + slot.methodSignature() + " because the callback object is being destructed.";
+ PyErr_WarnEx(PyExc_RuntimeWarning, message.constData(), 0);
+ return -1;
+ }
+
+ const bool setSenderDynamicProperty = !m_receiver.isNull();
+ if (setSenderDynamicProperty)
+ m_receiver->setProperty(senderDynamicProperty, QVariant::fromValue(sender()));
+
+ const bool isShortCuit = std::strchr(slot.methodSignature(), '(') == nullptr;
+ Shiboken::AutoDecRef callback(m_data->callback());
+ SignalManager::callPythonMetaMethod(slot, args, callback, isShortCuit);
+
+ if (setSenderDynamicProperty)
+ m_receiver->setProperty(senderDynamicProperty, QVariant{});
+
+ // SignalManager::callPythonMetaMethod might have failed, in that case we have to print the
+ // error so it considered "handled".
+ if (PyErr_Occurred()) {
+ int reclimit = Py_GetRecursionLimit();
+ // Inspired by Python's errors.c: PyErr_GivenExceptionMatches() function.
+ // Temporarily bump the recursion limit, so that PyErr_Print will not raise a recursion
+ // error again. Don't do it when the limit is already insanely high, to avoid overflow.
+ if (reclimit < (1 << 30))
+ Py_SetRecursionLimit(reclimit + 5);
+ PyErr_Print();
+ Py_SetRecursionLimit(reclimit);
+ }
+
+ return -1;
+}
+
+void GlobalReceiverV2::formatDebug(QDebug &debug) const
+{
+ debug << "receiver=" << m_receiver << ", slot=" << m_data;
+ if (isEmpty())
+ debug << ", empty";
+ else
+ debug << ", refs=" << m_refs;
+};
+
+QDebug operator<<(QDebug debug, const GlobalReceiverV2 *g)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "GlobalReceiverV2(";
+ if (g)
+ g->formatDebug(debug);
+ else
+ debug << '0';
+ debug << ')';
+ return debug;
+}
+
+} // namespace PySide