| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 Ford Motor Company |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtRemoteObjects module of the Qt Toolkit. |
| ** |
| ** $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 "qremoteobjectsource.h" |
| #include "qremoteobjectsource_p.h" |
| #include "qremoteobjectnode.h" |
| #include "qremoteobjectdynamicreplica.h" |
| |
| #include "qconnectionfactories_p.h" |
| #include "qremoteobjectsourceio_p.h" |
| #include "qremoteobjectabstractitemmodeladapter_p.h" |
| |
| #include <QtCore/qmetaobject.h> |
| #include <QtCore/qvarlengtharray.h> |
| #include <QtCore/qabstractitemmodel.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace QRemoteObjectPackets; |
| using namespace QRemoteObjectStringLiterals; |
| |
| const int QRemoteObjectSourceBase::qobjectPropertyOffset = QObject::staticMetaObject.propertyCount(); |
| const int QRemoteObjectSourceBase::qobjectMethodOffset = QObject::staticMetaObject.methodCount(); |
| static const QByteArray s_classinfoRemoteobjectSignature(QCLASSINFO_REMOTEOBJECT_SIGNATURE); |
| |
| QByteArray QtPrivate::qtro_classinfo_signature(const QMetaObject *metaObject) |
| { |
| if (!metaObject) |
| return QByteArray{}; |
| |
| for (int i = metaObject->classInfoOffset(); i < metaObject->classInfoCount(); ++i) { |
| auto ci = metaObject->classInfo(i); |
| if (s_classinfoRemoteobjectSignature == ci.name()) |
| return ci.value(); |
| } |
| return QByteArray{}; |
| } |
| |
| QRemoteObjectSourceBase::QRemoteObjectSourceBase(QObject *obj, Private *d, const SourceApiMap *api, |
| QObject *adapter) |
| : QObject(obj), |
| m_object(obj), |
| m_adapter(adapter), |
| m_api(api), |
| d(d) |
| { |
| if (!obj) { |
| qCWarning(QT_REMOTEOBJECT) << "QRemoteObjectSourceBase: Cannot replicate a NULL object" << m_api->name(); |
| return; |
| } |
| |
| setConnections(); |
| |
| const int nChildren = api->m_models.count() + api->m_subclasses.count(); |
| if (nChildren > 0) { |
| QVector<int> roles; |
| const int numProperties = api->propertyCount(); |
| int modelIndex = 0, subclassIndex = 0; |
| for (int i = 0; i < numProperties; ++i) { |
| if (api->isAdapterProperty(i)) |
| continue; |
| const int index = api->sourcePropertyIndex(i); |
| const auto property = m_object->metaObject()->property(index); |
| if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) { |
| auto propertyMeta = QMetaType::metaObjectForType(property.userType()); |
| QObject *child = property.read(m_object).value<QObject *>(); |
| if (propertyMeta->inherits(&QAbstractItemModel::staticMetaObject)) { |
| const auto modelInfo = api->m_models.at(modelIndex++); |
| QAbstractItemModel *model = qobject_cast<QAbstractItemModel *>(child); |
| QAbstractItemAdapterSourceAPI<QAbstractItemModel, QAbstractItemModelSourceAdapter> *modelApi = |
| new QAbstractItemAdapterSourceAPI<QAbstractItemModel, QAbstractItemModelSourceAdapter>(modelInfo.name); |
| if (!model) |
| m_children.insert(i, new QRemoteObjectSource(nullptr, d, modelApi, nullptr)); |
| else { |
| roles.clear(); |
| const auto knownRoles = model->roleNames(); |
| for (auto role : modelInfo.roles.split('|')) { |
| if (role.isEmpty()) |
| continue; |
| const int roleIndex = knownRoles.key(role, -1); |
| if (roleIndex == -1) { |
| qCWarning(QT_REMOTEOBJECT) << "Invalid role" << role << "for model" << model->metaObject()->className(); |
| qCWarning(QT_REMOTEOBJECT) << " known roles:" << knownRoles; |
| } else |
| roles << roleIndex; |
| } |
| auto adapter = new QAbstractItemModelSourceAdapter(model, nullptr, |
| roles.isEmpty() ? knownRoles.keys().toVector() : roles); |
| m_children.insert(i, new QRemoteObjectSource(model, d, modelApi, adapter)); |
| } |
| } else { |
| const auto classApi = api->m_subclasses.at(subclassIndex++); |
| m_children.insert(i, new QRemoteObjectSource(child, d, classApi, nullptr)); |
| } |
| } |
| } |
| } |
| } |
| |
| QRemoteObjectSource::QRemoteObjectSource(QObject *obj, Private *d, const SourceApiMap *api, QObject *adapter) |
| : QRemoteObjectSourceBase(obj, d, api, adapter) |
| , m_name(api->typeName() == QLatin1String("QAbstractItemModelAdapter") ? MODEL().arg(api->name()) : CLASS().arg(api->name())) |
| { |
| if (obj) |
| d->m_sourceIo->registerSource(this); |
| } |
| |
| QRemoteObjectRootSource::QRemoteObjectRootSource(QObject *obj, const SourceApiMap *api, |
| QObject *adapter, QRemoteObjectSourceIo *sourceIo) |
| : QRemoteObjectSourceBase(obj, new Private(sourceIo, this), api, adapter) |
| , m_name(api->name()) |
| { |
| d->m_sourceIo->registerSource(this); |
| } |
| |
| QRemoteObjectSourceBase::~QRemoteObjectSourceBase() |
| { |
| delete m_api; |
| } |
| |
| void QRemoteObjectSourceBase::setConnections() |
| { |
| const QMetaObject *meta = m_object->metaObject(); |
| for (int idx = 0; idx < m_api->signalCount(); ++idx) { |
| const int sourceIndex = m_api->sourceSignalIndex(idx); |
| |
| // This basically connects the parent Signals (note, all dynamic properties have onChange |
| //notifications, thus signals) to us. Normally each Signal is mapped to a unique index, |
| //but since we are forwarding them all, we keep the offset constant. |
| // |
| //We know no one will inherit from this class, so no need to worry about indices from |
| //derived classes. |
| const auto target = m_api->isAdapterSignal(idx) ? m_adapter : m_object; |
| if (!QMetaObject::connect(target, sourceIndex, this, QRemoteObjectSource::qobjectMethodOffset+idx, Qt::DirectConnection, 0)) { |
| qCWarning(QT_REMOTEOBJECT) << "QRemoteObjectSourceBase: QMetaObject::connect returned false. Unable to connect."; |
| return; |
| } |
| |
| qCDebug(QT_REMOTEOBJECT) << "Connection made" << idx << sourceIndex |
| << (m_api->isAdapterSignal(idx) |
| ? m_adapter->metaObject()->method(sourceIndex).name() |
| : meta->method(sourceIndex).name()); |
| } |
| } |
| |
| void QRemoteObjectSourceBase::resetObject(QObject *newObject) |
| { |
| if (m_object) |
| m_object->disconnect(this); |
| if (m_adapter) { |
| m_adapter->disconnect(this); |
| delete m_adapter; |
| m_adapter = nullptr; |
| } |
| // We need some dynamic replica specific code here, in case an object had null sub-classes that |
| // have been replaced with real objects. In this case, the ApiMap could be wrong and need updating. |
| if (newObject && qobject_cast<QRemoteObjectDynamicReplica *>(newObject) && m_api->isDynamic()) { |
| auto api = static_cast<const DynamicApiMap*>(m_api); |
| if (api->m_properties[0] == 0) { // 0 is an index into QObject itself, so this isn't a valid QtRO index |
| const auto rep = qobject_cast<QRemoteObjectDynamicReplica *>(newObject); |
| auto tmp = m_api; |
| m_api = new DynamicApiMap(newObject, rep->metaObject(), api->m_name, QLatin1String(rep->metaObject()->className())); |
| qCDebug(QT_REMOTEOBJECT) << " Reset m_api for" << api->m_name << "using new metaObject:" << rep->metaObject()->className(); |
| delete tmp; |
| } |
| } |
| |
| m_object = newObject; |
| auto model = qobject_cast<QAbstractItemModel *>(newObject); |
| if (model) { |
| d->m_sourceIo->registerSource(this); |
| m_adapter = new QAbstractItemModelSourceAdapter(model, nullptr, model->roleNames().keys().toVector()); |
| } |
| |
| setParent(newObject); |
| if (newObject) |
| setConnections(); |
| |
| const int nChildren = m_api->m_models.count() + m_api->m_subclasses.count(); |
| if (nChildren == 0) |
| return; |
| |
| if (!newObject) { |
| for (auto child : m_children) |
| child->resetObject(nullptr); |
| return; |
| } |
| |
| for (int i : m_children.keys()) { |
| const int index = m_api->sourcePropertyIndex(i); |
| const auto property = m_object->metaObject()->property(index); |
| QObject *child = property.read(m_object).value<QObject *>(); |
| m_children[i]->resetObject(child); |
| } |
| } |
| |
| QRemoteObjectSource::~QRemoteObjectSource() |
| { |
| for (auto it : m_children) { |
| // We used QPointers for m_children because we don't control the lifetime of child QObjects |
| // Since the this/source QObject's parent is the referenced QObject, it could have already |
| // been deleted |
| delete it; |
| } |
| } |
| |
| QRemoteObjectRootSource::~QRemoteObjectRootSource() |
| { |
| for (auto it : m_children) { |
| // We used QPointers for m_children because we don't control the lifetime of child QObjects |
| // Since the this/source QObject's parent is the referenced QObject, it could have already |
| // been deleted |
| delete it; |
| } |
| d->m_sourceIo->unregisterSource(this); |
| // removeListener tries to modify d->m_listeners, this is O(N²), |
| // so clear d->m_listeners prior to calling unregister (consume loop). |
| // We can do this, because we don't care about the return value of removeListener() here. |
| for (IoDeviceBase *io : qExchange(d->m_listeners, {})) { |
| removeListener(io, true); |
| } |
| delete d; |
| } |
| |
| QVariantList* QRemoteObjectSourceBase::marshalArgs(int index, void **a) |
| { |
| QVariantList &list = m_marshalledArgs; |
| int N = m_api->signalParameterCount(index); |
| if (N == 1 && QMetaType::typeFlags(m_api->signalParameterType(index, 0)).testFlag(QMetaType::PointerToQObject)) |
| N = 0; // Don't try to send pointers, the will be handle by QRO_ |
| if (list.size() < N) |
| list.reserve(N); |
| const int minFill = std::min(list.size(), N); |
| for (int i = 0; i < minFill; ++i) { |
| const int type = m_api->signalParameterType(index, i); |
| if (type == QMetaType::QVariant) |
| list[i] = *reinterpret_cast<QVariant *>(a[i + 1]); |
| else |
| list[i] = QVariant(type, a[i + 1]); |
| } |
| for (int i = list.size(); i < N; ++i) { |
| const int type = m_api->signalParameterType(index, i); |
| if (type == QMetaType::QVariant) |
| list << *reinterpret_cast<QVariant *>(a[i + 1]); |
| else |
| list << QVariant(type, a[i + 1]); |
| } |
| for (int i = N; i < list.size(); ++i) |
| list.removeLast(); |
| return &m_marshalledArgs; |
| } |
| |
| bool QRemoteObjectSourceBase::invoke(QMetaObject::Call c, int index, const QVariantList &args, QVariant* returnValue) |
| { |
| int status = -1; |
| int flags = 0; |
| bool forAdapter = (c == QMetaObject::InvokeMetaMethod ? m_api->isAdapterMethod(index) : m_api->isAdapterProperty(index)); |
| int resolvedIndex = (c == QMetaObject::InvokeMetaMethod ? m_api->sourceMethodIndex(index) : m_api->sourcePropertyIndex(index)); |
| if (resolvedIndex < 0) |
| return false; |
| QVarLengthArray<void*, 10> param(args.size() + 1); |
| |
| if (c == QMetaObject::InvokeMetaMethod) { |
| QMetaMethod method; |
| if (!forAdapter) |
| method = parent()->metaObject()->method(resolvedIndex); |
| |
| if (returnValue) { |
| if (!forAdapter && method.isValid() && method.returnType() == QMetaType::QVariant) |
| param[0] = const_cast<void*>(reinterpret_cast<const void*>(returnValue)); |
| else |
| param[0] = returnValue->data(); |
| } else { |
| param[0] = nullptr; |
| } |
| |
| auto argument = [&](int i) -> void * { |
| if ((forAdapter && m_api->methodParameterType(index, i) == QMetaType::QVariant) || |
| (method.isValid() && method.parameterType(i) == QMetaType::QVariant)) { |
| return const_cast<void*>(reinterpret_cast<const void*>(&args.at(i))); |
| } |
| return const_cast<void*>(args.at(i).data()); |
| }; |
| |
| for (int i = 0; i < args.size(); ++i) { |
| param[i + 1] = argument(i); |
| } |
| } else if (c == QMetaObject::WriteProperty || c == QMetaObject::ReadProperty) { |
| bool isQVariant = !forAdapter && parent()->metaObject()->property(resolvedIndex).userType() == QMetaType::QVariant; |
| for (int i = 0; i < args.size(); ++i) { |
| if (isQVariant) |
| param[i] = const_cast<void*>(reinterpret_cast<const void*>(&args.at(i))); |
| else |
| param[i] = const_cast<void*>(args.at(i).data()); |
| } |
| if (c == QMetaObject::WriteProperty) { |
| Q_ASSERT(param.size() == 2); // for return-value and setter value |
| // check QMetaProperty::write for an explanation of these |
| param.append(&status); |
| param.append(&flags); |
| } |
| } else { |
| // Better safe than sorry |
| return false; |
| } |
| int r = -1; |
| if (forAdapter) |
| r = m_adapter->qt_metacall(c, resolvedIndex, param.data()); |
| else |
| r = parent()->qt_metacall(c, resolvedIndex, param.data()); |
| return r == -1 && status == -1; |
| } |
| |
| void QRemoteObjectSourceBase::handleMetaCall(int index, QMetaObject::Call call, void **a) |
| { |
| if (d->m_listeners.empty()) |
| return; |
| |
| int propertyIndex = m_api->propertyIndexFromSignal(index); |
| if (propertyIndex >= 0) { |
| const int internalIndex = m_api->propertyRawIndexFromSignal(index); |
| const auto target = m_api->isAdapterProperty(internalIndex) ? m_adapter : m_object; |
| const QMetaProperty mp = target->metaObject()->property(propertyIndex); |
| qCDebug(QT_REMOTEOBJECT) << "Sending Invoke Property" << (m_api->isAdapterSignal(internalIndex) ? "via adapter" : "") << internalIndex << propertyIndex << mp.name() << mp.read(target); |
| |
| serializePropertyChangePacket(this, index); |
| d->m_packet.baseAddress = d->m_packet.size; |
| propertyIndex = internalIndex; |
| } |
| |
| qCDebug(QT_REMOTEOBJECT) << "# Listeners" << d->m_listeners.length(); |
| qCDebug(QT_REMOTEOBJECT) << "Invoke args:" << m_object |
| << (call == 0 ? QLatin1String("InvokeMetaMethod") : QStringLiteral("Non-invoked call: %d").arg(call)) |
| << m_api->signalSignature(index) << *marshalArgs(index, a); |
| |
| serializeInvokePacket(d->m_packet, name(), call, index, *marshalArgs(index, a), -1, propertyIndex); |
| d->m_packet.baseAddress = 0; |
| |
| for (IoDeviceBase *io : qAsConst(d->m_listeners)) |
| io->write(d->m_packet.array, d->m_packet.size); |
| } |
| |
| void QRemoteObjectRootSource::addListener(IoDeviceBase *io, bool dynamic) |
| { |
| d->m_listeners.append(io); |
| d->isDynamic = d->isDynamic || dynamic; |
| |
| if (dynamic) { |
| d->sentTypes.clear(); |
| serializeInitDynamicPacket(d->m_packet, this); |
| io->write(d->m_packet.array, d->m_packet.size); |
| } else { |
| serializeInitPacket(d->m_packet, this); |
| io->write(d->m_packet.array, d->m_packet.size); |
| } |
| } |
| |
| int QRemoteObjectRootSource::removeListener(IoDeviceBase *io, bool shouldSendRemove) |
| { |
| d->m_listeners.removeAll(io); |
| if (shouldSendRemove) |
| { |
| serializeRemoveObjectPacket(d->m_packet, m_api->name()); |
| io->write(d->m_packet.array, d->m_packet.size); |
| } |
| return d->m_listeners.length(); |
| } |
| |
| int QRemoteObjectSourceBase::qt_metacall(QMetaObject::Call call, int methodId, void **a) |
| { |
| methodId = QObject::qt_metacall(call, methodId, a); |
| if (methodId < 0) |
| return methodId; |
| |
| if (call == QMetaObject::InvokeMetaMethod) |
| handleMetaCall(methodId, call, a); |
| |
| return -1; |
| } |
| |
| DynamicApiMap::DynamicApiMap(QObject *object, const QMetaObject *metaObject, const QString &name, const QString &typeName) |
| : m_name(name), |
| m_typeName(typeName), |
| m_metaObject(metaObject), |
| m_cachedMetamethodIndex(-1) |
| { |
| m_enumOffset = metaObject->enumeratorOffset(); |
| m_enumCount = metaObject->enumeratorCount() - m_enumOffset; |
| |
| const int propCount = metaObject->propertyCount(); |
| const int propOffset = metaObject->propertyOffset(); |
| m_properties.reserve(propCount-propOffset); |
| int i = 0; |
| for (i = propOffset; i < propCount; ++i) { |
| const QMetaProperty property = metaObject->property(i); |
| if (QMetaType::typeFlags(property.userType()).testFlag(QMetaType::PointerToQObject)) { |
| auto propertyMeta = QMetaType::metaObjectForType(property.userType()); |
| QObject *child = property.read(object).value<QObject *>(); |
| if (propertyMeta->inherits(&QAbstractItemModel::staticMetaObject)) { |
| const QByteArray name = QByteArray::fromRawData(property.name(), |
| qstrlen(property.name())); |
| const QByteArray infoName = name.toUpper() + QByteArrayLiteral("_ROLES"); |
| const int infoIndex = metaObject->indexOfClassInfo(infoName.constData()); |
| QByteArray roleInfo; |
| if (infoIndex >= 0) { |
| auto ci = metaObject->classInfo(infoIndex); |
| roleInfo = QByteArray::fromRawData(ci.value(), qstrlen(ci.value())); |
| } |
| m_models << ModelInfo({qobject_cast<QAbstractItemModel *>(child), |
| QString::fromLatin1(property.name()), |
| roleInfo}); |
| } else { |
| const QMetaObject *meta = child ? child->metaObject() : propertyMeta; |
| QString typeName = QtRemoteObjects::getTypeNameAndMetaobjectFromClassInfo(meta); |
| if (typeName.isNull()) { |
| typeName = QString::fromLatin1(propertyMeta->className()); |
| // TODO better way to ensure we have consistent typenames between source/replicas? |
| if (typeName.endsWith(QLatin1String("Source"))) |
| typeName.chop(6); |
| } |
| |
| m_subclasses << new DynamicApiMap(child, meta, QString::fromLatin1(property.name()), typeName); |
| } |
| } |
| m_properties << i; |
| const int notifyIndex = metaObject->property(i).notifySignalIndex(); |
| if (notifyIndex != -1) { |
| m_signals << notifyIndex; |
| m_propertyAssociatedWithSignal.append(i-propOffset); |
| //The starting values of _signals will be the notify signals |
| //So if we are processing _signal with index i, api->sourcePropertyIndex(_propertyAssociatedWithSignal.at(i)) |
| //will be the property that changed. This is only valid if i < _propertyAssociatedWithSignal.size(). |
| } |
| } |
| const int methodCount = metaObject->methodCount(); |
| const int methodOffset = metaObject->methodOffset(); |
| for (i = methodOffset; i < methodCount; ++i) { |
| const QMetaMethod mm = metaObject->method(i); |
| const QMetaMethod::MethodType m = mm.methodType(); |
| if (m == QMetaMethod::Signal) { |
| if (m_signals.indexOf(i) >= 0) //Already added as a property notifier |
| continue; |
| m_signals << i; |
| } else if (m == QMetaMethod::Slot || m == QMetaMethod::Method) |
| m_methods << i; |
| } |
| |
| m_objectSignature = QtPrivate::qtro_classinfo_signature(metaObject); |
| } |
| |
| QList<QByteArray> DynamicApiMap::signalParameterNames(int index) const |
| { |
| const int objectIndex = m_signals.at(index); |
| checkCache(objectIndex); |
| return m_cachedMetamethod.parameterNames(); |
| } |
| |
| int DynamicApiMap::parameterCount(int objectIndex) const |
| { |
| checkCache(objectIndex); |
| return m_cachedMetamethod.parameterCount(); |
| } |
| |
| int DynamicApiMap::parameterType(int objectIndex, int paramIndex) const |
| { |
| checkCache(objectIndex); |
| return m_cachedMetamethod.parameterType(paramIndex); |
| } |
| |
| const QByteArray DynamicApiMap::signature(int objectIndex) const |
| { |
| checkCache(objectIndex); |
| return m_cachedMetamethod.methodSignature(); |
| } |
| |
| QMetaMethod::MethodType DynamicApiMap::methodType(int index) const |
| { |
| const int objectIndex = m_methods.at(index); |
| checkCache(objectIndex); |
| return m_cachedMetamethod.methodType(); |
| } |
| |
| const QByteArray DynamicApiMap::typeName(int index) const |
| { |
| const int objectIndex = m_methods.at(index); |
| checkCache(objectIndex); |
| return m_cachedMetamethod.typeName(); |
| } |
| |
| QList<QByteArray> DynamicApiMap::methodParameterNames(int index) const |
| { |
| const int objectIndex = m_methods.at(index); |
| checkCache(objectIndex); |
| return m_cachedMetamethod.parameterNames(); |
| } |
| |
| QT_END_NAMESPACE |