// 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 "qqmldebugconnection_p.h" #include "qqmldebugclient_p.h" #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE static const int protocolVersion = 1; static const QString serverId = QLatin1String("QDeclarativeDebugServer"); static const QString clientId = QLatin1String("QDeclarativeDebugClient"); class QQmlDebugConnectionPrivate : public QObjectPrivate { Q_DECLARE_PUBLIC(QQmlDebugConnection) public: QQmlDebugConnectionPrivate(); QPacketProtocol *protocol = nullptr; QIODevice *device = nullptr; QLocalServer *server = nullptr; QEventLoop handshakeEventLoop; QTimer handshakeTimer; bool gotHello = false; int currentDataStreamVersion = QDataStream::Qt_4_7; int maximumDataStreamVersion = QDataStream::Qt_DefaultCompiledVersion; QHash serverPlugins; QHash plugins; QStringList removedPlugins; void advertisePlugins(); void createProtocol(); void flush(); }; QQmlDebugConnectionPrivate::QQmlDebugConnectionPrivate() { handshakeTimer.setSingleShot(true); handshakeTimer.setInterval(3000); } void QQmlDebugConnectionPrivate::advertisePlugins() { Q_Q(QQmlDebugConnection); if (!q->isConnected()) return; QPacket pack(currentDataStreamVersion); pack << serverId << 1 << plugins.keys(); protocol->send(pack.data()); flush(); } void QQmlDebugConnection::socketConnected() { Q_D(QQmlDebugConnection); QPacket pack(d->currentDataStreamVersion); pack << serverId << 0 << protocolVersion << d->plugins.keys() << d->maximumDataStreamVersion << true; // We accept multiple messages per packet d->protocol->send(pack.data()); d->flush(); } void QQmlDebugConnection::socketDisconnected() { Q_D(QQmlDebugConnection); d->gotHello = false; emit disconnected(); } void QQmlDebugConnection::protocolReadyRead() { Q_D(QQmlDebugConnection); if (!d->gotHello) { QPacket pack(d->currentDataStreamVersion, d->protocol->read()); QString name; pack >> name; bool validHello = false; if (name == clientId) { int op = -1; pack >> op; if (op == 0) { int version = -1; pack >> version; if (version == protocolVersion) { QStringList pluginNames; QList pluginVersions; pack >> pluginNames; if (!pack.atEnd()) pack >> pluginVersions; const int pluginNamesSize = pluginNames.size(); const int pluginVersionsSize = pluginVersions.size(); for (int i = 0; i < pluginNamesSize; ++i) { float pluginVersion = 1.0; if (i < pluginVersionsSize) pluginVersion = pluginVersions.at(i); d->serverPlugins.insert(pluginNames.at(i), pluginVersion); } pack >> d->currentDataStreamVersion; validHello = true; } } } if (!validHello) { qWarning("QQmlDebugConnection: Invalid hello message"); close(); return; } d->gotHello = true; emit connected(); QHash::Iterator iter = d->plugins.begin(); for (; iter != d->plugins.end(); ++iter) { QQmlDebugClient::State newState = QQmlDebugClient::Unavailable; if (d->serverPlugins.contains(iter.key())) newState = QQmlDebugClient::Enabled; iter.value()->stateChanged(newState); } d->handshakeTimer.stop(); d->handshakeEventLoop.quit(); } while (d->protocol->packetsAvailable()) { QPacket pack(d->currentDataStreamVersion, d->protocol->read()); QString name; pack >> name; if (name == clientId) { int op = -1; pack >> op; if (op == 1) { // Service Discovery QHash oldServerPlugins = d->serverPlugins; d->serverPlugins.clear(); QStringList pluginNames; QList pluginVersions; pack >> pluginNames; if (!pack.atEnd()) pack >> pluginVersions; const int pluginNamesSize = pluginNames.size(); const int pluginVersionsSize = pluginVersions.size(); for (int i = 0; i < pluginNamesSize; ++i) { float pluginVersion = 1.0; if (i < pluginVersionsSize) pluginVersion = pluginVersions.at(i); d->serverPlugins.insert(pluginNames.at(i), pluginVersion); } QHash::Iterator iter = d->plugins.begin(); for (; iter != d->plugins.end(); ++iter) { const QString &pluginName = iter.key(); QQmlDebugClient::State newState = QQmlDebugClient::Unavailable; if (d->serverPlugins.contains(pluginName)) newState = QQmlDebugClient::Enabled; if (oldServerPlugins.contains(pluginName) != d->serverPlugins.contains(pluginName)) { iter.value()->stateChanged(newState); } } } else { qWarning() << "QQmlDebugConnection: Unknown control message id" << op; } } else { QHash::Iterator iter = d->plugins.find(name); if (iter == d->plugins.end()) { // We can get more messages for plugins we have removed because it takes time to // send the advertisement message but the removal is instant locally. if (!d->removedPlugins.contains(name)) { qWarning() << "QQmlDebugConnection: Message received for missing plugin" << name; } } else { QQmlDebugClient *client = *iter; QByteArray message; while (!pack.atEnd()) { pack >> message; client->messageReceived(message); } } } } } void QQmlDebugConnection::handshakeTimeout() { Q_D(QQmlDebugConnection); if (!d->gotHello) { qWarning() << "QQmlDebugConnection: Did not get handshake answer in time"; d->handshakeEventLoop.quit(); } } QQmlDebugConnection::QQmlDebugConnection(QObject *parent) : QObject(*(new QQmlDebugConnectionPrivate), parent) { Q_D(QQmlDebugConnection); connect(&d->handshakeTimer, &QTimer::timeout, this, &QQmlDebugConnection::handshakeTimeout); } QQmlDebugConnection::~QQmlDebugConnection() { Q_D(QQmlDebugConnection); QHash::iterator iter = d->plugins.begin(); for (; iter != d->plugins.end(); ++iter) emit iter.value()->stateChanged(QQmlDebugClient::NotConnected); } int QQmlDebugConnection::currentDataStreamVersion() const { Q_D(const QQmlDebugConnection); return d->currentDataStreamVersion; } void QQmlDebugConnection::setMaximumDataStreamVersion(int maximumVersion) { Q_D(QQmlDebugConnection); d->maximumDataStreamVersion = maximumVersion; } bool QQmlDebugConnection::isConnected() const { Q_D(const QQmlDebugConnection); return d->gotHello; } bool QQmlDebugConnection::isConnecting() const { Q_D(const QQmlDebugConnection); return !d->gotHello && d->device; } void QQmlDebugConnection::close() { Q_D(QQmlDebugConnection); if (d->gotHello) { d->gotHello = false; d->device->close(); QHash::iterator iter = d->plugins.begin(); for (; iter != d->plugins.end(); ++iter) emit iter.value()->stateChanged(QQmlDebugClient::NotConnected); } if (d->device) { d->device->deleteLater(); d->device = nullptr; } } bool QQmlDebugConnection::waitForConnected(int msecs) { Q_D(QQmlDebugConnection); auto socket = qobject_cast(d->device); if (!socket) { if (!d->server || (!d->server->hasPendingConnections() && !d->server->waitForNewConnection(msecs))) return false; } else if (!socket->waitForConnected(msecs)) { return false; } // wait for handshake d->handshakeTimer.start(); d->handshakeEventLoop.exec(); return d->gotHello; } QQmlDebugClient *QQmlDebugConnection::client(const QString &name) const { Q_D(const QQmlDebugConnection); return d->plugins.value(name, nullptr); } bool QQmlDebugConnection::addClient(const QString &name, QQmlDebugClient *client) { Q_D(QQmlDebugConnection); if (d->plugins.contains(name)) return false; d->removedPlugins.removeAll(name); d->plugins.insert(name, client); d->advertisePlugins(); return true; } bool QQmlDebugConnection::removeClient(const QString &name) { Q_D(QQmlDebugConnection); if (!d->plugins.contains(name)) return false; d->plugins.remove(name); d->removedPlugins.append(name); d->advertisePlugins(); return true; } float QQmlDebugConnection::serviceVersion(const QString &serviceName) const { Q_D(const QQmlDebugConnection); return d->serverPlugins.value(serviceName, -1); } bool QQmlDebugConnection::sendMessage(const QString &name, const QByteArray &message) { Q_D(QQmlDebugConnection); if (!isConnected() || !d->serverPlugins.contains(name)) return false; QPacket pack(d->currentDataStreamVersion); pack << name << message; d->protocol->send(pack.data()); d->flush(); return true; } void QQmlDebugConnectionPrivate::flush() { if (auto socket = qobject_cast(device)) socket->flush(); else if (auto socket = qobject_cast(device)) socket->flush(); } void QQmlDebugConnection::connectToHost(const QString &hostName, quint16 port) { Q_D(QQmlDebugConnection); if (d->gotHello) close(); auto socket = new QTcpSocket(this); d->device = socket; d->createProtocol(); connect(socket, &QAbstractSocket::disconnected, this, &QQmlDebugConnection::socketDisconnected); connect(socket, &QAbstractSocket::connected, this, &QQmlDebugConnection::socketConnected); connect(socket, static_cast( &QAbstractSocket::errorOccurred), this, &QQmlDebugConnection::socketError); connect(socket, &QAbstractSocket::stateChanged, this, &QQmlDebugConnection::socketStateChanged); socket->connectToHost(hostName, port); } void QQmlDebugConnection::startLocalServer(const QString &fileName) { Q_D(QQmlDebugConnection); if (d->gotHello) close(); if (d->server) d->server->deleteLater(); d->server = new QLocalServer(this); // QueuedConnection so that waitForNewConnection() returns true. connect(d->server, &QLocalServer::newConnection, this, &QQmlDebugConnection::newConnection, Qt::QueuedConnection); d->server->listen(fileName); } class LocalSocketSignalTranslator : public QObject { Q_OBJECT public: LocalSocketSignalTranslator(QLocalSocket *parent) : QObject(parent) { connect(parent, &QLocalSocket::stateChanged, this, &LocalSocketSignalTranslator::onStateChanged); connect(parent, &QLocalSocket::errorOccurred, this, &LocalSocketSignalTranslator::onError); } void onError(QLocalSocket::LocalSocketError error) { emit socketError(static_cast(error)); } void onStateChanged(QLocalSocket::LocalSocketState state) { emit socketStateChanged(static_cast(state)); } signals: void socketError(QAbstractSocket::SocketError); void socketStateChanged(QAbstractSocket::SocketState); }; void QQmlDebugConnection::newConnection() { Q_D(QQmlDebugConnection); delete d->device; QLocalSocket *socket = d->server->nextPendingConnection(); d->server->close(); d->device = socket; d->createProtocol(); connect(socket, &QLocalSocket::disconnected, this, &QQmlDebugConnection::socketDisconnected); auto translator = new LocalSocketSignalTranslator(socket); connect(translator, &LocalSocketSignalTranslator::socketError, this, &QQmlDebugConnection::socketError); connect(translator, &LocalSocketSignalTranslator::socketStateChanged, this, &QQmlDebugConnection::socketStateChanged); socketConnected(); } void QQmlDebugConnectionPrivate::createProtocol() { Q_Q(QQmlDebugConnection); delete protocol; protocol = new QPacketProtocol(device, q); QObject::connect(protocol, &QPacketProtocol::readyRead, q, &QQmlDebugConnection::protocolReadyRead); } QT_END_NAMESPACE #include #include "moc_qqmldebugconnection_p.cpp"