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.cpp737
1 files changed, 466 insertions, 271 deletions
diff --git a/sources/pyside6/libpyside/signalmanager.cpp b/sources/pyside6/libpyside/signalmanager.cpp
index 2c08f4328..557f130e0 100644
--- a/sources/pyside6/libpyside/signalmanager.cpp
+++ b/sources/pyside6/libpyside/signalmanager.cpp
@@ -1,51 +1,16 @@
-// -*- 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$
-**
-****************************************************************************/
+// 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 "signalmanager.h"
#include "pysidesignal.h"
+#include "pysidelogging_p.h"
#include "pysideproperty.h"
#include "pysideproperty_p.h"
-#include "pyside.h"
+#include "pysidecleanup.h"
#include "pyside_p.h"
#include "dynamicqmetaobject.h"
#include "pysidemetafunction_p.h"
+#include "pysidestaticstrings.h"
#include <autodecref.h>
#include <basewrapper.h>
@@ -54,19 +19,20 @@
#include <sbkconverter.h>
#include <sbkstring.h>
#include <sbkstaticstrings.h>
+#include <sbkerrors.h>
+#include <QtCore/QCoreApplication>
+#include <QtCore/QByteArrayView>
#include <QtCore/QDebug>
#include <QtCore/QHash>
+#include <QtCore/QScopedPointer>
+#include <QtCore/QTimerEvent>
#include <algorithm>
#include <limits>
+#include <memory>
-// 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
+using namespace Qt::StringLiterals;
#if QSLOT_CODE != 1 || QSIGNAL_CODE != 2
#error QSLOT_CODE and/or QSIGNAL_CODE changed! change the hardcoded stuff to the correct value!
@@ -75,26 +41,64 @@
#define PYSIDE_SIGNAL '2'
#include "globalreceiverv2.h"
-namespace {
- static PyObject *metaObjectAttr = nullptr;
-
- 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, nullptr);
- auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr);
- SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta);
- if (wrapper)
- Shiboken::BindingManager::instance().releaseWrapper(wrapper);
- delete meta;
+static PyObject *metaObjectAttr = nullptr;
+static PyObject *parseArguments(const QMetaMethod &method, void **args);
+static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *args);
+
+static bool qAppRunning = false;
+
+static void destroyMetaObject(PyObject *obj)
+{
+ void *ptr = PyCapsule_GetPointer(obj, nullptr);
+ auto meta = reinterpret_cast<PySide::MetaObjectBuilder *>(ptr);
+ SbkObject *wrapper = Shiboken::BindingManager::instance().retrieveWrapper(meta);
+ if (wrapper)
+ Shiboken::BindingManager::instance().releaseWrapper(wrapper);
+ delete meta;
+}
+
+static const char *metaCallName(QMetaObject::Call call)
+{
+ static const QHash<QMetaObject::Call, const char *> mapping = {
+ {QMetaObject::InvokeMetaMethod, "InvokeMetaMethod"},
+ {QMetaObject::ReadProperty, "ReadProperty"},
+ {QMetaObject::WriteProperty, "WriteProperty"},
+ {QMetaObject::ResetProperty, "ResetProperty"},
+ {QMetaObject::CreateInstance, "CreateInstance"},
+ {QMetaObject::IndexOfMethod, "IndexOfMethod"},
+ {QMetaObject::RegisterPropertyMetaType, "RegisterPropertyMetaType"},
+ {QMetaObject::RegisterMethodArgumentMetaType, "RegisterMethodArgumentMetaType"},
+ {QMetaObject::BindableProperty, "BindableProperty"},
+ {QMetaObject::CustomCall, "CustomCall"}
+ };
+ auto it = mapping.constFind(call);
+ return it != mapping.constEnd() ? it.value() : "<Unknown>";
+}
+
+static QByteArray methodSignature(const QMetaMethod &method)
+{
+ QByteArray result;
+ if (auto *t = method.typeName()) {
+ result += t;
+ result += ' ';
}
+ result += method.methodSignature();
+ return result;
}
-namespace PySide {
+static QByteArray msgCannotConvertParameter(const QMetaMethod &method, qsizetype p)
+{
+ return "Cannot call meta function \""_ba + methodSignature(method)
+ + "\" because parameter " + QByteArray::number(p) + " of type \""_ba
+ + method.parameterTypeName(p) + "\" cannot be converted."_ba;
+}
+
+static QByteArray msgCannotConvertReturn(const QMetaMethod &method)
+{
+ return "The return value of \""_ba + methodSignature(method) + "\" cannot be converted."_ba;
+}
+namespace PySide {
PyObjectWrapper::PyObjectWrapper()
:m_me(Py_None)
@@ -141,7 +145,8 @@ void PyObjectWrapper::reset(PyObject *o)
PyObjectWrapper &PyObjectWrapper::operator=(const PySide::PyObjectWrapper &other)
{
- reset(other.m_me);
+ if (this != &other)
+ reset(other.m_me);
return *this;
}
@@ -150,6 +155,14 @@ PyObjectWrapper::operator PyObject *() const
return m_me;
}
+
+int PyObjectWrapper::toInt() const
+{
+ // hold the GIL
+ Shiboken::GilState state;
+ return Shiboken::Enum::check(m_me) ? Shiboken::Enum::getValue(m_me) : -1;
+}
+
QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj)
{
if (Py_IsInitialized() == 0) {
@@ -164,7 +177,8 @@ QDataStream &operator<<(QDataStream &out, const PyObjectWrapper &myObj)
Shiboken::AutoDecRef pickleModule(PyImport_ImportModule("pickle"));
reduce_func = PyObject_GetAttr(pickleModule, Shiboken::PyName::dumps());
}
- Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, (PyObject *)myObj, NULL));
+ PyObject *pyObj = myObj;
+ Shiboken::AutoDecRef repr(PyObject_CallFunctionObjArgs(reduce_func, pyObj, nullptr));
if (repr.object()) {
const char *buff = nullptr;
Py_ssize_t size = 0;
@@ -208,29 +222,71 @@ QDataStream &operator>>(QDataStream &in, PyObjectWrapper &myObj)
};
+namespace PySide {
+using GlobalReceiverV2Ptr = std::shared_ptr<GlobalReceiverV2>;
+using GlobalReceiverV2Map = QHash<PySide::GlobalReceiverKey, GlobalReceiverV2Ptr>;
+}
+
using namespace PySide;
-struct SignalManager::SignalManagerPrivate
+// Listen for destroy() of main thread objects and ensure cleanup
+class SignalManagerDestroyListener : public QObject
{
- GlobalReceiverV2MapPtr m_globalReceivers;
+ Q_OBJECT
+public:
+ Q_DISABLE_COPY_MOVE(SignalManagerDestroyListener)
- SignalManagerPrivate() : m_globalReceivers(new GlobalReceiverV2Map{})
- {
- }
+ using QObject::QObject;
- ~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());
- }
+public Q_SLOTS:
+ void destroyNotify(const QObject *);
+
+protected:
+ void timerEvent(QTimerEvent *event) override;
+
+private:
+ int m_timerId = -1;
+};
+
+void SignalManagerDestroyListener::destroyNotify(const QObject *)
+{
+ if (qAppRunning && m_timerId == -1)
+ m_timerId = startTimer(0);
+}
+
+void SignalManagerDestroyListener::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == m_timerId) {
+ killTimer(std::exchange(m_timerId, -1));
+ SignalManager::instance().purgeEmptyGlobalReceivers();
}
+}
+
+struct SignalManager::SignalManagerPrivate
+{
+ Q_DISABLE_COPY_MOVE(SignalManagerPrivate)
+
+ SignalManagerPrivate() noexcept = default;
+ ~SignalManagerPrivate() { clear(); }
+
+ void deleteGlobalReceiver(const QObject *gr);
+ void clear();
+ void purgeEmptyGlobalReceivers();
+
+ GlobalReceiverV2Map m_globalReceivers;
+ static SignalManager::QmlMetaCallErrorHandler m_qmlMetaCallErrorHandler;
+
+ static void handleMetaCallError(QObject *object, int *result);
+ static int qtPropertyMetacall(QObject *object, QMetaObject::Call call,
+ int id, void **args);
+ static int qtMethodMetacall(QObject *object, int id, void **args);
+
+ QPointer<SignalManagerDestroyListener> m_listener;
};
+SignalManager::QmlMetaCallErrorHandler
+ SignalManager::SignalManagerPrivate::m_qmlMetaCallErrorHandler = nullptr;
+
static void clearSignalManager()
{
PySide::SignalManager::instance().clear();
@@ -240,7 +296,7 @@ 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)
+static PythonToCppFunc is_PyObject_PythonToCpp_PyObject_PTR_Convertible(PyObject * /* pyIn */)
{
return PyObject_PythonToCpp_PyObject_PTR;
}
@@ -259,6 +315,8 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate)
// Register PyObject type to use in queued signal and slot connections
qRegisterMetaType<PyObjectWrapper>("PyObject");
+ // Register QVariant(enum) conversion to QVariant(int)
+ QMetaType::registerConverter<PyObjectWrapper, int>(&PyObjectWrapper::toInt);
SbkConverter *converter = Shiboken::Conversions::createConverter(&PyBaseObject_Type, nullptr);
Shiboken::Conversions::setCppPointerToPythonFunction(converter, PyObject_PTR_CppToPython_PyObject);
@@ -276,8 +334,7 @@ SignalManager::SignalManager() : m_d(new SignalManagerPrivate)
void SignalManager::clear()
{
- delete m_d;
- m_d = new SignalManagerPrivate();
+ m_d->clear();
}
SignalManager::~SignalManager()
@@ -291,52 +348,123 @@ SignalManager &SignalManager::instance()
return me;
}
-QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback)
+void SignalManager::setQmlMetaCallErrorHandler(QmlMetaCallErrorHandler handler)
+{
+ SignalManagerPrivate::m_qmlMetaCallErrorHandler = handler;
+}
+
+static void qAppAboutToQuit()
+{
+ qAppRunning = false;
+ SignalManager::instance().purgeEmptyGlobalReceivers();
+}
+
+static bool isInMainThread(const QObject *o)
+{
+ if (o->isWidgetType() || o->isWindowType() || o->isQuickItemType())
+ return true;
+ auto *app = QCoreApplication::instance();
+ return app != nullptr && app->thread() == o->thread();
+}
+
+QObject *SignalManager::globalReceiver(QObject *sender, PyObject *callback, QObject *receiver)
{
- GlobalReceiverV2MapPtr globalReceivers = m_d->m_globalReceivers;
- GlobalReceiverKey key = GlobalReceiverV2::key(callback);
- GlobalReceiverV2 *gr = nullptr;
- auto it = globalReceivers->find(key);
- if (it == globalReceivers->end()) {
- gr = new GlobalReceiverV2(callback, globalReceivers);
- globalReceivers->insert(key, gr);
- if (sender) {
- gr->incRef(sender); // create a link reference
- gr->decRef(); // remove extra reference
+ if (m_d->m_listener.isNull() && !QCoreApplication::closingDown()) {
+ if (auto *app = QCoreApplication::instance()) {
+ // The signal manager potentially outlives QCoreApplication, ensure deletion
+ m_d->m_listener = new SignalManagerDestroyListener(app);
+ m_d->m_listener->setObjectName("qt_pyside_signalmanagerdestroylistener");
+ QObject::connect(app, &QCoreApplication::aboutToQuit, qAppAboutToQuit);
+ qAppRunning = true;
}
- } else {
- gr = it.value();
- if (sender)
- gr->incRef(sender);
}
- return reinterpret_cast<QObject *>(gr);
+ auto &globalReceivers = m_d->m_globalReceivers;
+ const GlobalReceiverKey key = GlobalReceiverV2::key(callback);
+ auto it = globalReceivers.find(key);
+ if (it == globalReceivers.end()) {
+ auto gr = std::make_shared<GlobalReceiverV2>(callback, receiver);
+ it = globalReceivers.insert(key, gr);
+ }
+
+ if (sender != nullptr) {
+ it.value()->incRef(sender); // create a link reference
+
+ // For main thread-objects, add a notification for destroy (PYSIDE-2646, 2141)
+ if (qAppRunning && !m_d->m_listener.isNull() && isInMainThread(sender)) {
+ QObject::connect(sender, &QObject::destroyed,
+ m_d->m_listener, &SignalManagerDestroyListener::destroyNotify,
+ Qt::UniqueConnection);
+ }
+ }
+
+ return it.value().get();
}
-int SignalManager::countConnectionsWith(const QObject *object)
+void SignalManager::purgeEmptyGlobalReceivers()
{
- 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;
+ m_d->purgeEmptyGlobalReceivers();
}
void SignalManager::notifyGlobalReceiver(QObject *receiver)
{
reinterpret_cast<GlobalReceiverV2 *>(receiver)->notify();
+ purgeEmptyGlobalReceivers();
}
void SignalManager::releaseGlobalReceiver(const QObject *source, QObject *receiver)
{
- auto gr = reinterpret_cast<GlobalReceiverV2 *>(receiver);
+ auto gr = static_cast<GlobalReceiverV2 *>(receiver);
gr->decRef(source);
+ if (gr->isEmpty())
+ m_d->deleteGlobalReceiver(gr);
+}
+
+void SignalManager::deleteGlobalReceiver(const QObject *gr)
+{
+ SignalManager::instance().m_d->deleteGlobalReceiver(gr);
+}
+
+void SignalManager::SignalManagerPrivate::deleteGlobalReceiver(const QObject *gr)
+{
+ for (auto it = m_globalReceivers.begin(), end = m_globalReceivers.end(); it != end; ++it) {
+ if (it.value().get() == gr) {
+ m_globalReceivers.erase(it);
+ break;
+ }
+ }
+}
+
+void SignalManager::SignalManagerPrivate::clear()
+{
+ // Delete receivers by always retrieving the current first element,
+ // because deleting a receiver can indirectly delete another one
+ // via ~DynamicSlotDataV2(). Using ~QHash/clear() could cause an
+ // iterator invalidation, and thus undefined behavior.
+ while (!m_globalReceivers.isEmpty())
+ m_globalReceivers.erase(m_globalReceivers.cbegin());
+}
+
+static bool isEmptyGlobalReceiver(const GlobalReceiverV2Ptr &g)
+{
+ return g->isEmpty();
+}
+
+void SignalManager::SignalManagerPrivate::purgeEmptyGlobalReceivers()
+{
+ // Delete repetitively (see comment in clear()).
+ while (true) {
+ auto it = std::find_if(m_globalReceivers.cbegin(), m_globalReceivers.cend(),
+ isEmptyGlobalReceiver);
+ if (it == m_globalReceivers.cend())
+ break;
+ m_globalReceivers.erase(it);
+ }
}
int SignalManager::globalReceiverSlotIndex(QObject *receiver, const char *signature) const
{
- return reinterpret_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature);
+ return static_cast<GlobalReceiverV2 *>(receiver)->addSlot(signature);
}
bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *args)
@@ -351,120 +479,159 @@ bool SignalManager::emitSignal(QObject *source, const char *signal, PyObject *ar
// 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 isShortCircuit
+ ? emitShortCircuitSignal(source, signalIndex, args)
+ : MetaFunction::call(source, signalIndex, args);
}
return false;
}
-int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args)
+// Handle errors from meta calls. Requires GIL and PyErr_Occurred()
+void SignalManager::SignalManagerPrivate::handleMetaCallError(QObject *object, int *result)
+{
+ // Bubbles Python exceptions up to the Javascript engine, if called from one
+ if (m_qmlMetaCallErrorHandler) {
+ auto idOpt = m_qmlMetaCallErrorHandler(object);
+ if (idOpt.has_value())
+ *result = idOpt.value();
+ }
+
+ const 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);
+}
+
+// Handler for QMetaObject::ReadProperty/WriteProperty/ResetProperty:
+int SignalManager::SignalManagerPrivate::qtPropertyMetacall(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;
- }
+ int result = id - metaObject->propertyCount();
+
+ const QMetaProperty mp = metaObject->property(id);
+
+ qCDebug(lcPySide).noquote().nospace() << __FUNCTION__
+ << ' ' << metaCallName(call) << " #" << id << ' ' << mp.typeName()
+ << "/\"" << mp.name() << "\" " << object;
+
+ if (!mp.isValid())
+ return result;
- 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;
+ Shiboken::GilState gil;
+ auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object);
+ Q_ASSERT(pySbkSelf);
+ auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf);
+ Shiboken::AutoDecRef pp_name(Shiboken::String::fromCString(mp.name()));
+ PySideProperty *pp = Property::getObject(pySelf, pp_name);
+ if (!pp) {
+ qWarning("Invalid property: %s.", mp.name());
+ return false;
+ }
+ pp->d->metaCall(pySelf, call, args);
+ Py_DECREF(pp);
+ if (PyErr_Occurred()) {
+ // PYSIDE-2160: An unknown type was reported. Indicated by StopIteration.
+ if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
+ PyObject *excType, *excValue, *excTraceback;
+ PyErr_Fetch(&excType, &excValue, &excTraceback);
+ bool ign = call == QMetaObject::WriteProperty;
+ PyErr_WarnFormat(PyExc_RuntimeWarning, 0,
+ ign ? "Unknown property type '%s' of QObject '%s' used in fset"
+ : "Unknown property type '%s' of QObject '%s' used in fget with %R",
+ pp->d->typeName.constData(), metaObject->className(), excValue);
+ if (PyErr_Occurred())
+ Shiboken::Errors::storeErrorOrPrint();
+ Py_DECREF(excType);
+ Py_DECREF(excValue);
+ Py_XDECREF(excTraceback);
+ return result;
}
+
+ qWarning().noquote().nospace()
+ << "An error occurred executing the property metacall " << call
+ << " on property \"" << mp.name() << "\" of " << object;
+ handleMetaCallError(object, &result);
}
+ return result;
+}
- 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;
+// Handler for QMetaObject::InvokeMetaMethod
+int SignalManager::SignalManagerPrivate::qtMethodMetacall(QObject *object,
+ int id, void **args)
+{
+ const QMetaObject *metaObject = object->metaObject();
+ const QMetaMethod method = metaObject->method(id);
+ int result = id - metaObject->methodCount();
- default:
- qWarning("Unsupported meta invocation type.");
- }
+ std::unique_ptr<Shiboken::GilState> gil;
- // 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.
+ qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << " #" << id
+ << " \"" << method.methodSignature() << '"';
- if (call == QMetaObject::InvokeMetaMethod) {
- id = id - methodCount;
+ if (method.methodType() == QMetaMethod::Signal) {
+ // emit python signal
+ QMetaObject::activate(object, id, args);
} else {
- id = id - propertyCount;
+ gil.reset(new Shiboken::GilState);
+ auto *pySbkSelf = Shiboken::BindingManager::instance().retrieveWrapper(object);
+ Q_ASSERT(pySbkSelf);
+ auto *pySelf = reinterpret_cast<PyObject *>(pySbkSelf);
+ QByteArray methodName = method.methodSignature();
+ methodName.truncate(methodName.indexOf('('));
+ Shiboken::AutoDecRef pyMethod(PyObject_GetAttrString(pySelf, methodName));
+ if (pyMethod.isNull()) {
+ PyErr_Format(PyExc_AttributeError, "Slot '%s::%s' not found.",
+ metaObject->className(), method.methodSignature().constData());
+ } else {
+ SignalManager::callPythonMetaMethod(method, args, pyMethod, false);
+ }
}
+ // 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 (pp || pp_name) {
- Shiboken::GilState gil;
- Py_XDECREF(pp);
- Py_XDECREF(pp_name);
- }
+ if (gil.get() == nullptr)
+ gil.reset(new Shiboken::GilState);
- // 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);
- }
- }
+ if (PyErr_Occurred())
+ handleMetaCallError(object, &result);
+
+ return result;
+}
+int SignalManager::qt_metacall(QObject *object, QMetaObject::Call call, int id, void **args)
+{
+ switch (call) {
+ case QMetaObject::ReadProperty:
+ case QMetaObject::WriteProperty:
+ case QMetaObject::ResetProperty:
+ id = SignalManagerPrivate::qtPropertyMetacall(object, call, id, args);
+ break;
+ case QMetaObject::RegisterPropertyMetaType:
+ case QMetaObject::BindableProperty:
+ id -= object->metaObject()->propertyCount();
+ break;
+ case QMetaObject::InvokeMetaMethod:
+ id = SignalManagerPrivate::qtMethodMetacall(object, id, args);
+ break;
+ case QMetaObject::CreateInstance:
+ case QMetaObject::IndexOfMethod:
+ case QMetaObject::RegisterMethodArgumentMetaType:
+ case QMetaObject::CustomCall:
+ qCDebug(lcPySide).noquote().nospace() << __FUNCTION__ << ' '
+ << metaCallName(call) << " #" << id << ' ' << object;
+ id -= object->metaObject()->methodCount();
+ break;
+#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
+ case QMetaObject::ConstructInPlace:
+ break;
+#endif
+ }
return id;
}
@@ -473,35 +640,27 @@ int SignalManager::callPythonMetaMethod(const QMetaMethod &method, void **args,
Q_ASSERT(pyMethod);
Shiboken::GilState gil;
- PyObject *pyArguments = nullptr;
-
- if (isShortCuit){
- pyArguments = reinterpret_cast<PyObject *>(args[1]);
- } else {
- pyArguments = parseArguments(method.parameterTypes(), args);
- }
+ PyObject *pyArguments = isShortCuit
+ ? reinterpret_cast<PyObject *>(args[1]) : parseArguments(method, args);
if (pyArguments) {
- Shiboken::Conversions::SpecificConverter *retConverter = nullptr;
+ QScopedPointer<Shiboken::Conversions::SpecificConverter> retConverter;
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);
+ if (returnType != nullptr && returnType[0] != 0 && std::strcmp("void", returnType) != 0) {
+ retConverter.reset(new Shiboken::Conversions::SpecificConverter(returnType));
+ if (!retConverter->isValid()) {
+ PyErr_SetString(PyExc_RuntimeError, msgCannotConvertReturn(method).constData());
return -1;
}
}
Shiboken::AutoDecRef retval(PyObject_CallObject(pyMethod, pyArguments));
- if (!isShortCuit && pyArguments){
+ if (!isShortCuit && pyArguments)
Py_DECREF(pyArguments);
- }
- if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter) {
+ if (!retval.isNull() && retval != Py_None && !PyErr_Occurred() && retConverter)
retConverter->toCpp(retval, args[0]);
- }
- delete retConverter;
}
return -1;
@@ -531,6 +690,57 @@ static MetaObjectBuilder *metaBuilderFromDict(PyObject *dict)
return reinterpret_cast<MetaObjectBuilder *>(PyCapsule_GetPointer(pyBuilder, nullptr));
}
+// Helper to format a method signature "foo(QString)" into
+// Slot decorator "@Slot(str)"
+
+struct slotSignature
+{
+ explicit slotSignature(const char *signature) : m_signature(signature) {}
+
+ const char *m_signature;
+};
+
+QDebug operator<<(QDebug debug, const slotSignature &sig)
+{
+ QDebugStateSaver saver(debug);
+ debug.noquote();
+ debug.nospace();
+ debug << "@Slot(";
+ QByteArrayView signature(sig.m_signature);
+ const auto len = signature.size();
+ auto pos = signature.indexOf('(');
+ if (pos != -1 && pos < len - 2) {
+ ++pos;
+ while (true) {
+ auto nextPos = signature.indexOf(',', pos);
+ if (nextPos == -1)
+ nextPos = len - 1;
+ const QByteArrayView parameter = signature.sliced(pos, nextPos - pos);
+ if (parameter == "QString") {
+ debug << "str";
+ } else if (parameter == "double") {
+ debug << "float";
+ } else {
+ const bool hasDelimiter = parameter.contains("::");
+ if (hasDelimiter)
+ debug << '"';
+ if (!hasDelimiter && parameter.endsWith('*'))
+ debug << parameter.first(parameter.size() - 1);
+ else
+ debug << parameter;
+ if (hasDelimiter)
+ debug << '"';
+ }
+ pos = nextPos + 1;
+ if (pos >= len)
+ break;
+ debug << ',';
+ }
+ }
+ debug << ')';
+ return debug;
+}
+
int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signature, QMetaMethod::MethodType type)
{
if (!source) {
@@ -544,26 +754,32 @@ int SignalManager::registerMetaMethodGetIndex(QObject *source, const char *signa
if (methodIndex == -1) {
SbkObject *self = Shiboken::BindingManager::instance().retrieveWrapper(source);
if (!Shiboken::Object::hasCppWrapper(self)) {
- qWarning() << "Invalid Signal signature:" << signature;
+ qWarning().noquote().nospace() << __FUNCTION__
+ << ": Cannot add dynamic method \"" << signature << "\" (" << type
+ << ") to " << source << ": No Wrapper found.";
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, nullptr, destroyMetaObject);
- PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo);
- Py_DECREF(pyDmo);
- }
+ }
+ auto *pySelf = reinterpret_cast<PyObject *>(self);
+ auto *dict = SbkObject_GetDict_NoRef(pySelf);
+ MetaObjectBuilder *dmo = metaBuilderFromDict(dict);
+
+ // Create a instance meta object
+ if (!dmo) {
+ dmo = new MetaObjectBuilder(Py_TYPE(pySelf), metaObject);
+ PyObject *pyDmo = PyCapsule_New(dmo, nullptr, destroyMetaObject);
+ PyObject_SetAttr(pySelf, metaObjectAttr, pyDmo);
+ Py_DECREF(pyDmo);
+ }
- if (type == QMetaMethod::Signal)
- return dmo->addSignal(signature);
- else
- return dmo->addSlot(signature);
+ if (type == QMetaMethod::Slot) {
+ qCWarning(lcPySide).noquote().nospace()
+ << "Warning: Registering dynamic slot \""
+ << signature << "\" on \"" << source->metaObject()->className()
+ << "\". Consider annotating with " << slotSignature(signature);
}
+
+ return type == QMetaMethod::Signal
+ ? dmo->addSignal(signature) : dmo->addSlot(signature);
}
return methodIndex;
}
@@ -580,51 +796,30 @@ const QMetaObject *SignalManager::retrieveMetaObject(PyObject *self)
// m_dirty flag is set.
Q_ASSERT(self);
- MetaObjectBuilder *builder = metaBuilderFromDict(reinterpret_cast<SbkObject *>(self)->ob_dict);
+ auto *ob_dict = SbkObject_GetDict_NoRef(self);
+ MetaObjectBuilder *builder = metaBuilderFromDict(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)
+static PyObject *parseArguments(const QMetaMethod &method, void **args)
{
- int argsSize = paramTypes.count();
+ const auto &paramTypes = method.parameterTypes();
+ const qsizetype argsSize = paramTypes.size();
PyObject *preparedArgs = PyTuple_New(argsSize);
- for (int i = 0, max = argsSize; i < max; ++i) {
+ for (qsizetype i = 0; i < argsSize; ++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);
+ auto param = paramTypes.at(i);
+ Shiboken::Conversions::SpecificConverter converter(param.constData());
+ if (!converter) {
+ PyErr_SetString(PyExc_TypeError, msgCannotConvertParameter(method, i).constData());
Py_DECREF(preparedArgs);
- return 0;
+ return nullptr;
}
+ PyTuple_SET_ITEM(preparedArgs, i, converter.toPython(data));
}
return preparedArgs;
}
@@ -636,4 +831,4 @@ static bool emitShortCircuitSignal(QObject *source, int signalIndex, PyObject *a
return true;
}
-} //namespace
+#include "signalmanager.moc"