aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpyside/signalmanager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpyside/signalmanager.cpp')
-rw-r--r--sources/pyside6/libpyside/signalmanager.cpp640
1 files changed, 640 insertions, 0 deletions
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp
new file mode 100644
index 000000000..0992cfcfd
--- /dev/null
+++ b/sources/pyside6/libpyside/signalmanager.cpp
@@ -0,0 +1,640 @@
+// -*- mode: cpp;-*-
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt for Python.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "signalmanager.h"
+#include "pysidesignal.h"
+#include "pysideproperty.h"
+#include "pysideproperty_p.h"
+#include "pyside.h"
+#include "pyside_p.h"
+#include "dynamicqmetaobject.h"
+#include "pysidemetafunction_p.h"
+
+#include <autodecref.h>
+#include <basewrapper.h>
+#include <bindingmanager.h>
+#include <gilstate.h>
+#include <sbkconverter.h>
+#include <sbkstring.h>
+#include <sbkstaticstrings.h>
+
+#include <QtCore/QDebug>
+#include <QtCore/QHash>
+
+#include <algorithm>
+#include <limits>
+
+// These private headers are needed to throw JavaScript exceptions
+#if PYSIDE_QML_PRIVATE_API_SUPPORT
+ #include <private/qv4engine_p.h>
+ #include <private/qv4context_p.h>
+ #include <private/qqmldata_p.h>
+#endif
+
+#if QSLOT_CODE != 1 || QSIGNAL_CODE != 2
+#error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value!
+#endif
+#define PYSIDE_SLOT '1'
+#define PYSIDE_SIGNAL '2'
+#include "globalreceiverv2.h"
+
+namespace {
+ static PyObject *metaObjectAttr = 0;
+
+ static int callMethod(QObject *object, int id, void **args);
+ static PyObject *parseArguments(const QList< QByteArray >& paramTypes, void **args);
+ static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args);
+
+ static void destroyMetaObject(PyObject *obj)
+ {
+ void *ptr = PyCapsule_GetPointer(obj, 0);
+ auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr);
+ SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta);
+ if (wrapper)
+ Shiboken::BindingManager::instance().releaseWrapper(wrapper);
+ delete meta;
+ }
+}
+
+namespace PySide {
+
+
+PyObjectWrapper::PyObjectWrapper()
+ :m_me(Py_None)
+{
+ // PYSIDE-813: When PYSIDE-164 was solved by adding some thread allowance,
+ // this code was no longer protected. It was hard to find this connection.
+ // See the website https://bugreports.qt.io/browse/PYSIDE-813 for details.
+ Shiboken::GilState gil;
+ Py_XINCREF(m_me);
+}
+
+PyObjectWrapper::PyObjectWrapper(PyObject *me)
+ : m_me(me)
+{
+ Shiboken::GilState gil;
+ Py_XINCREF(m_me);
+}
+
+PyObjectWrapper::PyObjectWrapper(const PyObjectWrapper &other)
+ : m_me(other.m_me)
+{
+ Shiboken::GilState gil;
+ Py_XINCREF(m_me);
+}
+
+PyObjectWrapper::~PyObjectWrapper()
+{
+ // Check that Python is still initialized as sometimes this is called by a static destructor
+ // after Python interpeter is shutdown.
+ if (!Py_IsInitialized())
+ return;
+
+ Shiboken::GilState gil;
+ Py_XDECREF(m_me);
+}
+
+void PyObjectWrapper::reset(PyObject *o)
+{
+ Shiboken::GilState gil;
+ Py_XINCREF(o);
+ Py_XDECREF(m_me);
+ m_me = o;
+}
+
+PyObjectWrapper &PyObjectWrapper::operator=(const PySide::PyObjectWrapper &other)
+{
+ reset(other.m_me);
+ return *this;
+}
+
+PyObjectWrapper::operator PyObject *() const
+{
+ return m_me;
+}
+
+QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj)
+{
+ if (Py_IsInitialized() == 0) {
+ qWarning() << "Stream operator for PyObject called without python interpreter.";
+ return out;
+ }
+
+ static PyObject *reduce_func = 0;
+
+ Shiboken::GilState gil;
+ if (!reduce_func) {
+ Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle"));
+ reduce_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::dumps());
+ }
+ Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, (PyObject *)myObj, NULL));
+ if (repr.object()) {
+ const char *buff = nullptr;
+ Py_ssize_t size = 0;
+ if (PyBytes_Check(repr.object())) {
+ buff = PyBytes_AS_STRING(repr.object());
+ size = PyBytes_GET_SIZE(repr.object());
+ } else if (Shiboken::String::check(repr.object())) {
+ buff = Shiboken::String::toCString(repr.object());
+ size = Shiboken::String::len(repr.object());
+ }
+ QByteArray data(buff, size);
+ out << data;
+ }
+ return out;
+}
+
+QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj)
+{
+ if (Py_IsInitialized() == 0) {
+ qWarning() << "Stream operator for PyObject called without python interpreter.";
+ return in;
+ }
+
+ static PyObject *eval_func = 0;
+
+ Shiboken::GilState gil;
+ if (!eval_func) {
+ Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle"));
+ eval_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::loads());
+ }
+
+ QByteArray repr;
+ in >> repr;
+ Shiboken::AutoDecRef pyCode(PyBytes_FromStringAndSize(repr.data(), repr.size()));
+ Shiboken::AutoDecRef value(PyObject_CallFunctionObjArgs(eval_func, pyCode.object(), 0));
+ if (!value.object())
+ value.reset(Py_None);
+ myObj.reset(value);
+ return in;
+}
+
+};
+
+using namespace PySide;
+
+struct SignalManager::SignalManagerPrivate
+{
+ SharedMap m_globalReceivers;
+
+ SignalManagerPrivate()
+ {
+ m_globalReceivers = SharedMap( new QMap<QByteArray, GlobalReceiverV2 *>() );
+ }
+
+ ~SignalManagerPrivate()
+ {
+ if (!m_globalReceivers.isNull()) {
+ // Delete receivers by always retrieving the current first element, because deleting a
+ // receiver can indirectly delete another one, and if we use qDeleteAll, that could
+ // cause either a double delete, or iterator invalidation, and thus undefined behavior.
+ while (!m_globalReceivers->isEmpty())
+ delete *m_globalReceivers->cbegin();
+ Q_ASSERT(m_globalReceivers->isEmpty());
+ }
+ }
+};
+
+static void clearSignalManager()
+{
+ PySide::SignalManager::instance().clear();
+}
+
+static void PyObject_PythonToCpp_PyObject_PTR(PyObject *pyIn, void *cppOut)
+{
+ *reinterpret_cast<PyObject **>(cppOut) = pyIn;
+}
+static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject *pyIn)
+{
+ return PyObject_PythonToCpp_PyObject_PTR;
+}
+static PyObject *PyObject_PTR_CppToPython_PyObject(const void *cppIn)
+{
+ auto pyOut = reinterpret_cast<PyObject *>(const_cast<void *>(cppIn));
+ if (pyOut)
+ Py_INCREF(pyOut);
+ return pyOut;
+}
+
+SignalManager::SignalManager() : m_d(new SignalManagerPrivate)
+{
+ // Register Qt primitive typedefs used on signals.
+ using namespace Shiboken;
+
+ // Register PyObject type to use in queued signal and slot connections
+ qRegisterMetaType<PyObjectWrapper>("PyObject");
+
+ SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
+ Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
+ Shiboken::Conversions::setPythonToCppPointerFunctions(converter, PyObject_PythonToCpp_PyObject_PTR, is_PyObject_PythonToCpp_PyObject_PTR_Convertible);
+ Shiboken::Conversions::registerConverterName(converter, "PyObject");
+ Shiboken::Conversions::registerConverterName(converter, "object");
+ Shiboken::Conversions::registerConverterName(converter, "PyObjectWrapper");
+ Shiboken::Conversions::registerConverterName(converter, "PySide::PyObjectWrapper");
+
+ PySide::registerCleanupFunction(clearSignalManager);
+
+ if (!metaObjectAttr)
+ metaObjectAttr = Shiboken::String::fromCString("__METAOBJECT__");
+}
+
+void SignalManager::clear()
+{
+ delete m_d;
+ m_d = new SignalManagerPrivate();
+}
+
+SignalManager::~SignalManager()
+{
+ delete m_d;
+}
+
+SignalManager &SignalManager::instance()
+{
+ static SignalManager me;
+ return me;
+}
+
+QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback)
+{
+ SharedMap globalReceivers = m_d->m_globalReceivers;
+ QByteArray hash = GlobalReceiverV2::hash(callback);
+ GlobalReceiverV2 *gr = nullptr;
+ auto it = globalReceivers->find(hash);
+ if (it == globalReceivers->end()) {
+ gr = new GlobalReceiverV2(callback, globalReceivers);
+ globalReceivers->insert(hash, gr);
+ if (sender) {
+ gr->incRef(sender); // create a link reference
+ gr->decRef(); // remove extra reference
+ }
+ } else {
+ gr = it.value();
+ if (sender)
+ gr->incRef(sender);
+ }
+
+ return reinterpret_cast<QObject *>(gr);
+}
+
+int SignalManager::countConnectionsWith(const QObject *object)
+{
+ int count = 0;
+ for (GlobalReceiverV2Map::const_iterator it = m_d->m_globalReceivers->cbegin(), end = m_d->m_globalReceivers->cend(); it != end; ++it) {
+ if (it.value()->refCount(object))
+ count++;
+ }
+ return count;
+}
+
+void SignalManager::notifyGlobalReceiver(QObject *receiver)
+{
+ reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify();
+}
+
+void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver)
+{
+ auto gr = reinterpret_cast<GlobalReceiverV2 *>(receiver);
+ gr->decRef(source);
+}
+
+int SignalManager::globalReceiverSlotIndex(QObject *receiver, const char *signature) const
+{
+ return reinterpret_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature);
+}
+
+bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args)
+{
+ if (!Signal::checkQtSignal(signal))
+ return false;
+ signal++;
+
+ int signalIndex = source->metaObject()->indexOfSignal(signal);
+ if (signalIndex != -1) {
+ // cryptic but works!
+ // if the signature doesn't have a '(' it's a shor circuited signal, i.e. std::find
+ // returned the string null terminator.
+ bool isShortCircuit = !*std::find(signal, signal + std::strlen(signal), '(');
+ if (isShortCircuit)
+ return emitShortCircuitSignal(source, signalIndex, args);
+ else
+ return MetaFunction::call(source, signalIndex, args);
+ }
+ return false;
+}
+
+int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args)
+{
+ const QMetaObject *metaObject = object->metaObject();
+ PySideProperty *pp = nullptr;
+ PyObject *pp_name = nullptr;
+ QMetaProperty mp;
+ PyObject *pySelf = nullptr;
+ int methodCount = metaObject->methodCount();
+ int propertyCount = metaObject->propertyCount();
+
+ if (call != QMetaObject::InvokeMetaMethod) {
+ mp = metaObject->property(id);
+ if (!mp.isValid()) {
+ return id - methodCount;
+ }
+
+ Shiboken::GilState gil;
+ pySelf = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object));
+ Q_ASSERT(pySelf);
+ pp_name = Shiboken::String::fromCString(mp.name());
+ pp = Property::getObject(pySelf, pp_name);
+ if (!pp) {
+ qWarning("Invalid property: %s.", mp.name());
+ Py_XDECREF(pp_name);
+ return id - methodCount;
+ }
+ }
+
+ switch(call) {
+#ifndef QT_NO_PROPERTIES
+ case QMetaObject::ReadProperty:
+ case QMetaObject::WriteProperty:
+ case QMetaObject::ResetProperty:
+ pp->d->metaCallHandler(pp, pySelf, call, args);
+ break;
+#endif
+ case QMetaObject::InvokeMetaMethod:
+ id = callMethod(object, id, args);
+ break;
+
+ default:
+ qWarning("Unsupported meta invocation type.");
+ }
+
+ // WARNING Isn't safe to call any metaObject and/or object methods beyond this point
+ // because the object can be deleted inside the called slot.
+
+ if (call == QMetaObject::InvokeMetaMethod) {
+ id = id - methodCount;
+ } else {
+ id = id - propertyCount;
+ }
+
+ if (pp || pp_name) {
+ Shiboken::GilState gil;
+ Py_XDECREF(pp);
+ Py_XDECREF(pp_name);
+ }
+
+ // Bubbles Python exceptions up to the Javascript engine, if called from one
+ {
+ Shiboken::GilState gil;
+
+ if (PyErr_Occurred()) {
+
+#if PYSIDE_QML_PRIVATE_API_SUPPORT
+ // This JS engine grabber based off of Qt 5.5's `qjsEngine` function
+ QQmlData *data = QQmlData::get(object, false);
+
+ if (data && !data->jsWrapper.isNullOrUndefined()) {
+ QV4::ExecutionEngine *engine = data->jsWrapper.engine();
+ if (engine->currentStackFrame != nullptr) {
+ PyObject *errType, *errValue, *errTraceback;
+ PyErr_Fetch(&errType, &errValue, &errTraceback);
+ // PYSIDE-464: The error is only valid before PyErr_Restore,
+ // PYSIDE-464: therefore we take local copies.
+ Shiboken::AutoDecRef objStr(PyObject_Str(errValue));
+ const QString errString = QLatin1String(Shiboken::String::toCString(objStr));
+ const bool isSyntaxError = errType == PyExc_SyntaxError;
+ const bool isTypeError = errType == PyExc_TypeError;
+ PyErr_Restore(errType, errValue, errTraceback);
+
+ PyErr_Print(); // Note: PyErr_Print clears the error.
+
+ if (isSyntaxError) {
+ return engine->throwSyntaxError(errString);
+ } else if (isTypeError) {
+ return engine->throwTypeError(errString);
+ } else {
+ return engine->throwError(errString);
+ }
+ }
+ }
+#endif // PYSIDE_QML_PRIVATE_API_SUPPORT
+
+ 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 id;
+}
+
+int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args, PyObject *pyMethod, bool isShortCuit)
+{
+ Q_ASSERT(pyMethod);
+
+ Shiboken::GilState gil;
+ PyObject *pyArguments = nullptr;
+
+ if (isShortCuit){
+ pyArguments = reinterpret_cast<PyObject *>(args[1]);
+ } else {
+ pyArguments = parseArguments(method.parameterTypes(), args);
+ }
+
+ if (pyArguments) {
+ Shiboken::Conversions::SpecificConverter *retConverter = nullptr;
+ const char *returnType = method.typeName();
+ if (returnType && std::strcmp("", returnType) && std::strcmp("void", returnType)) {
+ retConverter = new Shiboken::Conversions::SpecificConverter(returnType);
+ if (!retConverter || !*retConverter) {
+ PyErr_Format(PyExc_RuntimeError, "Can't find converter for '%s' to call Python meta method.", returnType);
+ return -1;
+ }
+ }
+
+ Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments));
+
+ if (!isShortCuit && pyArguments){
+ Py_DECREF(pyArguments);
+ }
+
+ if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) {
+ retConverter->toCpp(retval, args[0]);
+ }
+ delete retConverter;
+ }
+
+ return -1;
+}
+
+bool SignalManager::registerMetaMethod(QObject *source, const char *signature, QMetaMethod::MethodType type)
+{
+ int ret = registerMetaMethodGetIndex(source, signature, type);
+ return (ret != -1);
+}
+
+static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict)
+{
+ // PYSIDE-803: The dict in this function is the ob_dict of an SbkObject.
+ // The "metaObjectAttr" entry is only handled in this file. There is no
+ // way in this function to involve the interpreter. Therefore, we need
+ // no GIL.
+ // Note that "SignalManager::registerMetaMethodGetIndex" has write actions
+ // that might involve the interpreter, but in that context the GIL is held.
+ if (!dict || !PyDict_Contains(dict, metaObjectAttr))
+ return nullptr;
+
+ // PYSIDE-813: The above assumption is not true in debug mode:
+ // PyDict_GetItem would touch PyThreadState_GET and the global error state.
+ // PyDict_GetItemWithError instead can work without GIL.
+ PyObject *pyBuilder = PyDict_GetItemWithError(dict, metaObjectAttr);
+ return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr));
+}
+
+int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signature, QMetaMethod::MethodType type)
+{
+ if (!source) {
+ qWarning("SignalManager::registerMetaMethodGetIndex(\"%s\") called with source=nullptr.",
+ signature);
+ return -1;
+ }
+ const QMetaObject *metaObject = source->metaObject();
+ int methodIndex = metaObject->indexOfMethod(signature);
+ // Create the dynamic signal is needed
+ if (methodIndex == -1) {
+ SbkObject *self = Shiboken::BindingManager::instance().retrieveWrapper(source);
+ if (!Shiboken::Object::hasCppWrapper(self)) {
+ qWarning() << "Invalid Signal signature:" << signature;
+ return -1;
+ } else {
+ auto pySelf = reinterpret_cast<PyObject *>(self);
+ PyObject *dict = self->ob_dict;
+ MetaObjectBuilder *dmo = metaBuilderFromDict(dict);
+
+ // Create a instance meta object
+ if (!dmo) {
+ dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject);
+ PyObject *pyDmo = PyCapsule_New(dmo, 0, destroyMetaObject);
+ PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo);
+ Py_DECREF(pyDmo);
+ }
+
+ if (type == QMetaMethod::Signal)
+ return dmo->addSignal(signature);
+ else
+ return dmo->addSlot(signature);
+ }
+ }
+ return methodIndex;
+}
+
+const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self)
+{
+ // PYSIDE-803: Avoid the GIL in SignalManager::retrieveMetaObject
+ // This function had the GIL. We do not use the GIL unless we have to.
+ // metaBuilderFromDict accesses a Python dict, but in that context there
+ // is no way to reach the interpreter, see "metaBuilderFromDict".
+ //
+ // The update function is MetaObjectBuilderPrivate::update in
+ // dynamicmetaobject.c . That function now uses the GIL when the
+ // m_dirty flag is set.
+ Q_ASSERT(self);
+
+ MetaObjectBuilder *builder = metaBuilderFromDict(reinterpret_cast<SbkObject *>(self)->ob_dict);
+ if (!builder)
+ builder = &(retrieveTypeUserData(self)->mo);
+
+ return builder->update();
+}
+
+namespace {
+
+static int callMethod(QObject *object, int id, void **args)
+{
+ const QMetaObject *metaObject = object->metaObject();
+ QMetaMethod method = metaObject->method(id);
+
+ if (method.methodType() == QMetaMethod::Signal) {
+ // emit python signal
+ QMetaObject::activate(object, id, args);
+ } else {
+ Shiboken::GilState gil;
+ auto self = reinterpret_cast<PyObject *>(Shiboken::BindingManager::instance().retrieveWrapper(object));
+ QByteArray methodName = method.methodSignature();
+ methodName.truncate(methodName.indexOf('('));
+ Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(self, methodName));
+ return SignalManager::callPythonMetaMethod(method, args, pyMethod, false);
+ }
+ return -1;
+}
+
+
+static PyObject *parseArguments(const QList<QByteArray>& paramTypes, void **args)
+{
+ int argsSize = paramTypes.count();
+ PyObject *preparedArgs = PyTuple_New(argsSize);
+
+ for (int i = 0, max = argsSize; i < max; ++i) {
+ void *data = args[i+1];
+ const char *dataType = paramTypes[i].constData();
+ Shiboken::Conversions::SpecificConverter converter(dataType);
+ if (converter) {
+ PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data));
+ } else {
+ PyErr_Format(PyExc_TypeError, "Can't call meta function because I have no idea how to handle %s", dataType);
+ Py_DECREF(preparedArgs);
+ return 0;
+ }
+ }
+ return preparedArgs;
+}
+
+static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args)
+{
+ void *signalArgs[2] = {nullptr, args};
+ source->qt_metacall(QMetaObject::InvokeMetaMethod, signalIndex, signalArgs);
+ return true;
+}
+
+} //namespace