aboutsummaryrefslogtreecommitdiffstats
path: root/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp')
-rw-r--r--sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp506
1 files changed, 506 insertions, 0 deletions
diff --git a/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp b/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp
new file mode 100644
index 000000000..941e38c6e
--- /dev/null
+++ b/sources/pyside6/libpysideremoteobjects/pysidedynamicclass.cpp
@@ -0,0 +1,506 @@
+// Copyright (C) 2025 Ford Motor Company
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// Workaround to access protected functions. PySide builds with this, but
+// since this is now a separate library, we need to add it too.
+
+#include "pysidedynamiccommon_p.h"
+#include "pysidedynamicclass_p.h"
+#include "pysidecapsulemethod_p.h"
+#include "pysiderephandler_p.h"
+
+#include <basewrapper.h>
+#include <sbkconverter.h>
+#include <sbkstring.h>
+
+#include <pyside_p.h>
+#include <pysideproperty.h>
+#include <pysideqobject.h>
+#include <pysidesignal.h>
+#include <pysideutils.h>
+
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qvariantlist.h>
+
+#include <QtRemoteObjects/qremoteobjectpendingcall.h>
+#include <QtRemoteObjects/qremoteobjectreplica.h>
+
+using namespace Shiboken;
+
+class FriendlyReplica : public QRemoteObjectReplica
+{
+public:
+ using QRemoteObjectReplica::send;
+ using QRemoteObjectReplica::setProperties;
+ using QRemoteObjectReplica::propAsVariant;
+ using QRemoteObjectReplica::sendWithReply;
+};
+
+extern "C"
+{
+
+PyObject *propertiesAttr()
+{
+ static PyObject *const s = Shiboken::String::createStaticString("__PROPERTIES__");
+ return s;
+}
+
+struct SourceDefs
+{
+ static PyTypeObject *getSbkType()
+ {
+ static PyTypeObject *sbkType =
+ Shiboken::Conversions::getPythonTypeObject("QObject");
+ return sbkType;
+ }
+
+ static PyObject *getBases()
+ {
+ static PyObject *bases = PyTuple_Pack(1, getSbkType());
+ return bases;
+ }
+
+ static const char *getTypePrefix()
+ {
+ return "2:PySide6.QtRemoteObjects.DynamicSource.";
+ }
+
+ static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+ {
+ static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(), Py_tp_init));
+ int res = initFunc(self, args, kwds);
+ if (res < 0) {
+ PyErr_Print();
+ return res;
+ }
+
+ // Get the properties from the type
+ PyTypeObject *type = Py_TYPE(self);
+ auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+ if (!pyProperties) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+ return -1;
+ }
+ // Add a copy of the properties to the object
+ auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+ auto *propertiesCopy = new QVariantList(*propPtr);
+ PyObject *capsule = PyCapsule_New(propertiesCopy, nullptr, [](PyObject *capsule) {
+ delete reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(capsule, nullptr));
+ });
+ PyObject_SetAttr(self, propertiesAttr(), capsule);
+ Py_DECREF(capsule);
+ return res;
+ }
+
+ static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+ {
+ auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+ "Payload"));
+ if (!methodData) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload.");
+ return nullptr;
+ }
+ PyObject *self = methodData->self;
+ if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+ // Handle property getter/setter against our hidden properties attribute
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+ if (capsule) {
+ auto *ob_dict = SbkObject_GetDict_NoRef(self);
+ auto *propPtr = PyCapsule_GetPointer(PyDict_GetItem(ob_dict, propertiesAttr()),
+ nullptr);
+ auto *currentProperties = reinterpret_cast<QVariantList *>(propPtr);
+ auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+ if (callData->indexInObject < 0
+ || callData->indexInObject >= currentProperties->size()) {
+ PyErr_Format(PyExc_RuntimeError, "Unknown property method: %s",
+ callData->name.constData());
+ return nullptr;
+ }
+ const QVariant &currentVariant = currentProperties->at(callData->indexInObject);
+
+ // Handle getter
+ if (PyTuple_Size(args) == 0)
+ return toPython(currentVariant);
+
+ // Handle setter
+ if (PyTuple_Size(args) != 1) {
+ PyErr_SetString(PyExc_TypeError, "Property setter takes exactly one argument");
+ return nullptr;
+ }
+ Conversions::SpecificConverter converter(currentVariant.metaType().name());
+ QVariant variant{currentVariant.metaType()};
+ auto metaType = currentVariant.metaType();
+ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+ converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+ variant.convert(metaType);
+ } else {
+ converter.toCpp(PyTuple_GetItem(args, 0), variant.data());
+ }
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ if (variant == currentVariant)
+ Py_RETURN_NONE;
+
+ currentProperties->replace(callData->indexInObject, variant);
+ // Get the QMetaObject and emit the property changed signal if there is one
+ const auto *metaObject = PySide::retrieveMetaObject(self);
+ auto metaProperty = metaObject->property(callData->propertyIndex);
+ if (metaProperty.hasNotifySignal()) {
+ // We know our custom types don't have multiple cpp objects
+ void *cptr = reinterpret_cast<SbkObject *>(self)->d->cptr[0];
+ auto *qObject = reinterpret_cast<QObject *>(cptr);
+ void *_args[] = {nullptr, variant.data()};
+ QMetaObject::activate(qObject, metaProperty.notifySignalIndex(), _args);
+ }
+ Py_RETURN_NONE;
+ }
+ }
+ if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+ auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+ if (callData->name.startsWith("push") && callData->name.size() > 4) {
+ const auto *metaObject = PySide::retrieveMetaObject(self);
+ // The convention for QtRO is if a property is named "something" and uses
+ // push, the name of the push method will be "pushSomething". But it is
+ // possible the name would be "Something", so we need to check upper
+ // and lower case.
+ auto name = callData->name.sliced(4);
+ auto index = metaObject->indexOfProperty(name.constData());
+ if (index < 0) {
+ name[0] = tolower(name[0]); // Try lower case
+ index = metaObject->indexOfProperty(name.constData());
+ }
+ // It is possible a .rep names a Slot "push" or "pushSomething" that
+ // isn't generated for a property. Let that fall through to regular
+ // method handling.
+ if (index >= 0) {
+ // Call the custom descriptor's set method
+ auto result = PyObject_SetAttrString(self, name.constData(),
+ PyTuple_GetItem(args, 0));
+ if (result < 0) {
+ PyErr_Print();
+ return nullptr;
+ }
+ Py_RETURN_NONE;
+ }
+ }
+ // TODO: This doesn't do much, as it is "eaten" by a PyError_Print in
+ // SignalManager::handleMetaCallError()
+ // Is there a better way to address slots that need to be implemented?
+ PyErr_Format(PyExc_NotImplementedError, "** The method %s is not implemented",
+ callData->name.constData());
+ return nullptr;
+ }
+
+ PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+ return nullptr;
+ }
+};
+
+struct ReplicaDefs
+{
+ static PyTypeObject *getSbkType()
+ {
+ static PyTypeObject *sbkType =
+ Shiboken::Conversions::getPythonTypeObject("QRemoteObjectReplica");
+ return sbkType;
+ }
+
+ static PyObject *getBases()
+ {
+ static PyObject *bases = PyTuple_Pack(1, getSbkType());
+ return bases;
+ }
+
+ static const char *getTypePrefix()
+ {
+ return "2:PySide6.QtRemoteObjects.DynamicReplica.";
+ }
+
+ static int tp_init(PyObject *self, PyObject *args, PyObject *kwds)
+ {
+ static initproc initFunc = reinterpret_cast<initproc>(PepType_GetSlot(getSbkType(),
+ Py_tp_init));
+ QRemoteObjectReplica *replica = nullptr;
+ if (PyTuple_Size(args) == 0) {
+ if (initFunc(self, args, kwds) < 0)
+ return -1;
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ } else { // Process replica with arguments passed from the added node.acquire method
+ PyObject *node = nullptr;
+ PyObject *constructorType = nullptr;
+ PyObject *name = nullptr;
+ static PyTypeObject *nodeType = Shiboken::Conversions::getPythonTypeObject("QRemoteObjectNode");
+ if (!PyArg_UnpackTuple(args, "Replica.__init__", 2, 3, &node, &constructorType, &name) ||
+ !PySide::inherits(Py_TYPE(node), nodeType->tp_name)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Replicas can be initialized with no arguments or by node.acquire only");
+ return -1;
+ }
+ static auto *constructorArgs = PyTuple_Pack(1, constructorType);
+ if (initFunc(self, constructorArgs, kwds) < 0)
+ return -1;
+ if (name)
+ PyObject_CallMethod(self, "initializeNode", "OO", node, name);
+ else
+ PyObject_CallMethod(self, "initializeNode", "O", node);
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ }
+ if (!replica) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to initialize replica");
+ return -1;
+ }
+ // Get the properties from the type
+ PyTypeObject *type = Py_TYPE(self);
+ auto *pyProperties = PyObject_GetAttr(reinterpret_cast<PyObject *>(type), propertiesAttr());
+ if (!pyProperties) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to get properties from type");
+ return -1;
+ }
+ // Make a copy of the properties and set them on the replica
+ auto *propPtr = reinterpret_cast<QVariantList *>(PyCapsule_GetPointer(pyProperties, nullptr));
+ auto propertiesCopy = QVariantList(*propPtr);
+ static_cast<FriendlyReplica *>(replica)->setProperties(std::move(propertiesCopy));
+ return 0;
+ }
+
+ static PyObject *capsule_method_handler(PyObject *payload, PyObject *args)
+ {
+ auto *methodData = reinterpret_cast<CapsuleDescriptorData *>(PyCapsule_GetPointer(payload,
+ "Payload"));
+ if (!methodData) {
+ PyErr_SetString(PyExc_RuntimeError, "Invalid call to dynamic method. Missing payload.");
+ return nullptr;
+ }
+ PyObject *self = methodData->self;
+ QRemoteObjectReplica *replica = nullptr;
+ Shiboken::Conversions::pythonToCppPointer(getSbkType(), self, &replica);
+ if (PyCapsule_IsValid(methodData->payload, "PropertyCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "PropertyCapsule");
+ if (capsule) {
+ auto *callData = reinterpret_cast<PropertyCapsule *>(capsule);
+ QVariant currentVariant = static_cast<FriendlyReplica *>(replica)->propAsVariant(callData->indexInObject);
+
+ // Handle getter
+ if (PyTuple_Size(args) == 0) // Getter
+ return toPython(currentVariant);
+
+ // Handle setter - currentVariant is a copy, so we can modify it
+ if (PyTuple_Size(args) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "Property setter takes exactly one argument");
+ return nullptr;
+ }
+ Conversions::SpecificConverter converter(currentVariant.metaType().name());
+ auto metaType = currentVariant.metaType();
+ if (metaType.flags().testFlag(QMetaType::IsEnumeration)) {
+ converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+ currentVariant.convert(metaType);
+ } else {
+ converter.toCpp(PyTuple_GetItem(args, 0), currentVariant.data());
+ }
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ QVariantList _args{currentVariant};
+ static_cast<FriendlyReplica *>(replica)->send(QMetaObject::WriteProperty, callData->propertyIndex, _args);
+ Py_RETURN_NONE;
+ }
+ }
+ if (PyCapsule_IsValid(methodData->payload, "MethodCapsule")) {
+ auto *capsule = PyCapsule_GetPointer(methodData->payload, "MethodCapsule");
+ if (capsule) {
+ auto *callData = reinterpret_cast<MethodCapsule *>(capsule);
+ if (PyTuple_Size(args) != callData->argumentTypes.size()) {
+ PyErr_SetString(PyExc_TypeError,
+ "Method called with incorrect number of arguments");
+ return nullptr;
+ }
+ QVariantList _args;
+ static Conversions::SpecificConverter argsConverter("QVariantList");
+ argsConverter.toCpp(args, &_args);
+ if (PyErr_Occurred()) // POD conversion can produce an error
+ return nullptr;
+ if (!callData->returnType.isValid() ||
+ (callData->returnType.isValid() && callData->returnType.id() == QMetaType::Void)) {
+ static_cast<FriendlyReplica *>(replica)->send(QMetaObject::InvokeMetaMethod, callData->methodIndex, _args);
+ Py_RETURN_NONE;
+ }
+ QRemoteObjectPendingCall *cppResult = new QRemoteObjectPendingCall;
+ *cppResult = static_cast<FriendlyReplica *>(replica)->sendWithReply(QMetaObject::InvokeMetaMethod,
+ callData->methodIndex, _args);
+ static PyTypeObject *baseType =
+ Shiboken::Conversions::getPythonTypeObject("QRemoteObjectPendingCall");
+ Q_ASSERT(baseType);
+ auto *pyResult = Shiboken::Object::newObject(baseType, cppResult, true, true);
+ return pyResult;
+ }
+ }
+
+ PyErr_SetString(PyExc_RuntimeError, "Unknown capsule type");
+ return nullptr;
+ }
+};
+
+static int DynamicType_traverse(PyObject *self, visitproc visit, void *arg)
+{
+ auto traverseProc = reinterpret_cast<traverseproc>(PepType_GetSlot(SbkObject_TypeF(),
+ Py_tp_traverse));
+ return traverseProc(self, visit, arg);
+}
+
+static int DynamicType_clear(PyObject *self)
+{
+ auto clearProc = reinterpret_cast<inquiry>(PepType_GetSlot(SbkObject_TypeF(), Py_tp_clear));
+ return clearProc(self);
+}
+
+static PyMethodDef DynamicClass_methods[] = {
+ {"get_enum", reinterpret_cast<PyCFunction>(DynamicType_get_enum), METH_O | METH_CLASS,
+ "Get enum type by name"},
+ {nullptr, nullptr, 0, nullptr}
+};
+
+static PyType_Slot DynamicClass_slots[] = {
+ {Py_tp_base, nullptr}, // inserted by introduceWrapperType
+ {Py_tp_init, nullptr}, // inserted by createDynamicType
+ {Py_tp_traverse, reinterpret_cast<void *>(DynamicType_traverse)},
+ {Py_tp_clear, reinterpret_cast<void *>(DynamicType_clear)},
+ {Py_tp_methods, reinterpret_cast<void *>(DynamicClass_methods)},
+ {0, nullptr}
+};
+
+} // extern "C"
+
+template <typename T, typename BaseType>
+PyTypeObject *createDynamicClassImpl(QMetaObject *meta)
+{
+ DynamicClass_slots[1].pfunc = reinterpret_cast<void*>(T::tp_init);
+
+ auto fullTypeName = QByteArray{T::getTypePrefix()} + meta->className();
+ PyType_Spec spec = {
+ fullTypeName.constData(),
+ 0,
+ 0,
+ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
+ DynamicClass_slots
+ };
+
+ auto type = Shiboken::ObjectType::introduceWrapperType(
+ reinterpret_cast<PyObject *>(PySideRepFile_TypeF()),
+ meta->className(),
+ meta->className(),
+ &spec,
+ &Shiboken::callCppDestructor<BaseType>,
+ T::getBases(),
+ Shiboken::ObjectType::WrapperFlags::InternalWrapper);
+
+ auto *self = reinterpret_cast<PyObject *>(type);
+ if (create_managed_py_enums(self, meta) < 0)
+ return nullptr;
+
+ PySide::Signal::registerSignals(type, meta);
+ Shiboken::ObjectType::setSubTypeInitHook(type, &PySide::initQObjectSubType);
+ PySide::initDynamicMetaObject(type, meta, 0); // Size 0?
+
+ PyMethodDef method = {
+ nullptr,
+ reinterpret_cast<PyCFunction>(T::capsule_method_handler),
+ METH_VARARGS,
+ nullptr
+ };
+ for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) {
+ // Create a PropertyCapsule for each property to store the info needed for
+ // the handler. Assign the __get__ and (if needed) __set__ attributes to a
+ // PySideProperty which becomes the attribute set on the new type.
+ auto metaProperty = meta->property(i);
+ PyObject *kwds = PyDict_New();
+ auto metaType = metaProperty.metaType();
+ auto *pyPropertyType = PyUnicode_FromString(metaType.name());
+ PyDict_SetItemString(kwds, "type", pyPropertyType);
+ Py_DECREF(pyPropertyType);
+
+ method.ml_name = metaProperty.name();
+ auto *pc = new PropertyCapsule{metaProperty.name(), i, i - meta->propertyOffset()};
+ auto capsule = PyCapsule_New(pc, "PropertyCapsule", [](PyObject *capsule) {
+ delete static_cast<PropertyCapsule *>(PyCapsule_GetPointer(capsule, "PropertyCapsule"));
+ });
+ auto capsulePropObject = make_capsule_property(&method, capsule,
+ metaProperty.isWritable());
+ PyObject *fget = PyObject_GetAttrString(capsulePropObject, "__get__");
+ PyDict_SetItemString(kwds, "fget", fget);
+ if (metaProperty.isWritable()) {
+ PyObject *fset = PyObject_GetAttrString(capsulePropObject, "__set__");
+ PyDict_SetItemString(kwds, "fset", fset);
+ if (metaProperty.hasNotifySignal()) {
+ auto nameString = metaProperty.notifySignal().name();
+ auto *notify = PyObject_GetAttrString(reinterpret_cast<PyObject *>(type),
+ nameString.constData());
+ PyDict_SetItemString(kwds, "notify", notify);
+ }
+ }
+ PyObject *pyProperty = PyObject_Call(reinterpret_cast<PyObject *>(PySideProperty_TypeF()),
+ PyTuple_New(0), kwds);
+ if (PyObject_SetAttrString(reinterpret_cast<PyObject *>(type),
+ metaProperty.name(), pyProperty) < 0) {
+ return nullptr;
+ }
+ Py_DECREF(pyProperty);
+ }
+ for (int i = meta->methodOffset(); i < meta->methodCount(); ++i) {
+ // Create a CapsuleMethod for each Slot method to store the info needed
+ // for the handler.
+ auto metaMethod = meta->method(i);
+ // Note: We are creating our custom metatype ourselves, which makes our added
+ // (non-signal), methods return QMetaMethod::MethodType::Method, not
+ // MethodType::Slot. This is fine, we just need to create a CapsuleMethod
+ // for those methods.
+ if (metaMethod.methodType() == QMetaMethod::MethodType::Signal)
+ continue;
+ auto name = metaMethod.name();
+ method.ml_name = name.constData();
+ QList<QMetaType> argumentTypes;
+ for (int j = 0; j < metaMethod.parameterCount(); ++j)
+ argumentTypes << metaMethod.parameterMetaType(j);
+ MethodCapsule *capsuleData = new MethodCapsule{metaMethod.name(),
+ metaMethod.methodIndex(),
+ std::move(argumentTypes),
+ metaMethod.returnMetaType()};
+ add_capsule_method_to_type(type, &method,
+ PyCapsule_New(capsuleData, "MethodCapsule",
+ [](PyObject *capsule) {
+ delete reinterpret_cast<MethodCapsule *>(PyCapsule_GetPointer(capsule, "MethodCapsule"));
+ }));
+ }
+
+ return type;
+}
+
+PyTypeObject *createDynamicClass(QMetaObject *meta, PyObject *properties_capsule)
+{
+ bool isSource;
+ if (strncmp(meta->superClass()->className(), "QObject", 7) == 0) {
+ isSource = true;
+ } else if (strncmp(meta->superClass()->className(), "QRemoteObjectReplica", 20) == 0) {
+ isSource = false;
+ } else {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Dynamic type must be a subclass of QObject or QRemoteObjectReplica");
+ return nullptr;
+ }
+
+ PyTypeObject *newType = nullptr;
+
+ if (isSource)
+ newType = createDynamicClassImpl<SourceDefs, QObject>(meta);
+ else
+ newType = createDynamicClassImpl<ReplicaDefs, QRemoteObjectReplica>(meta);
+
+ // Add the properties to the new type as an attribute
+ if (PyObject_SetAttr(reinterpret_cast<PyObject *>(newType), propertiesAttr(),
+ properties_capsule) < 0) {
+ Py_DECREF(newType);
+ return nullptr;
+ }
+
+ return newType;
+}