1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmldebugconnection_p.h"
5#include "qqmldebugclient_p.h"
6
7#include <private/qpacketprotocol_p.h>
8#include <private/qpacket_p.h>
9#include <private/qobject_p.h>
10
11#include <QtCore/qeventloop.h>
12#include <QtCore/qtimer.h>
13#include <QtCore/QHash>
14#include <QtCore/qdatastream.h>
15#include <QtNetwork/qlocalserver.h>
16#include <QtNetwork/qlocalsocket.h>
17#include <QtNetwork/qtcpsocket.h>
18
19QT_BEGIN_NAMESPACE
20
21static const int protocolVersion = 1;
22static const QString serverId = QLatin1String("QDeclarativeDebugServer");
23static const QString clientId = QLatin1String("QDeclarativeDebugClient");
24
25class QQmlDebugConnectionPrivate : public QObjectPrivate
26{
27 Q_DECLARE_PUBLIC(QQmlDebugConnection)
28
29public:
30 QQmlDebugConnectionPrivate();
31 QPacketProtocol *protocol = nullptr;
32 QIODevice *device = nullptr;
33 QLocalServer *server = nullptr;
34 QEventLoop handshakeEventLoop;
35 QTimer handshakeTimer;
36
37 bool gotHello = false;
38 int currentDataStreamVersion = QDataStream::Qt_4_7;
39 int maximumDataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
40 QHash <QString, float> serverPlugins;
41 QHash<QString, QQmlDebugClient *> plugins;
42 QStringList removedPlugins;
43
44 void advertisePlugins();
45 void createProtocol();
46 void flush();
47};
48
49QQmlDebugConnectionPrivate::QQmlDebugConnectionPrivate()
50{
51 handshakeTimer.setSingleShot(true);
52 handshakeTimer.setInterval(3000);
53}
54
55void QQmlDebugConnectionPrivate::advertisePlugins()
56{
57 Q_Q(QQmlDebugConnection);
58 if (!q->isConnected())
59 return;
60
61 QPacket pack(currentDataStreamVersion);
62 pack << serverId << 1 << plugins.keys();
63 protocol->send(data: pack.data());
64 flush();
65}
66
67void QQmlDebugConnection::socketConnected()
68{
69 Q_D(QQmlDebugConnection);
70 QPacket pack(d->currentDataStreamVersion);
71 pack << serverId << 0 << protocolVersion << d->plugins.keys() << d->maximumDataStreamVersion
72 << true; // We accept multiple messages per packet
73 d->protocol->send(data: pack.data());
74 d->flush();
75}
76
77void QQmlDebugConnection::socketDisconnected()
78{
79 Q_D(QQmlDebugConnection);
80 d->gotHello = false;
81 emit disconnected();
82}
83
84void QQmlDebugConnection::protocolReadyRead()
85{
86 Q_D(QQmlDebugConnection);
87 if (!d->gotHello) {
88 QPacket pack(d->currentDataStreamVersion, d->protocol->read());
89 QString name;
90
91 pack >> name;
92
93 bool validHello = false;
94 if (name == clientId) {
95 int op = -1;
96 pack >> op;
97 if (op == 0) {
98 int version = -1;
99 pack >> version;
100 if (version == protocolVersion) {
101 QStringList pluginNames;
102 QList<float> pluginVersions;
103 pack >> pluginNames;
104 if (!pack.atEnd())
105 pack >> pluginVersions;
106
107 const int pluginNamesSize = pluginNames.size();
108 const int pluginVersionsSize = pluginVersions.size();
109 for (int i = 0; i < pluginNamesSize; ++i) {
110 float pluginVersion = 1.0;
111 if (i < pluginVersionsSize)
112 pluginVersion = pluginVersions.at(i);
113 d->serverPlugins.insert(key: pluginNames.at(i), value: pluginVersion);
114 }
115
116 pack >> d->currentDataStreamVersion;
117 validHello = true;
118 }
119 }
120 }
121
122 if (!validHello) {
123 qWarning(msg: "QQmlDebugConnection: Invalid hello message");
124 close();
125 return;
126 }
127 d->gotHello = true;
128 emit connected();
129
130 QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.begin();
131 for (; iter != d->plugins.end(); ++iter) {
132 QQmlDebugClient::State newState = QQmlDebugClient::Unavailable;
133 if (d->serverPlugins.contains(key: iter.key()))
134 newState = QQmlDebugClient::Enabled;
135 iter.value()->stateChanged(state: newState);
136 }
137
138 d->handshakeTimer.stop();
139 d->handshakeEventLoop.quit();
140 }
141
142 while (d->protocol->packetsAvailable()) {
143 QPacket pack(d->currentDataStreamVersion, d->protocol->read());
144 QString name;
145 pack >> name;
146
147 if (name == clientId) {
148 int op = -1;
149 pack >> op;
150
151 if (op == 1) {
152 // Service Discovery
153 QHash<QString, float> oldServerPlugins = d->serverPlugins;
154 d->serverPlugins.clear();
155
156 QStringList pluginNames;
157 QList<float> pluginVersions;
158 pack >> pluginNames;
159 if (!pack.atEnd())
160 pack >> pluginVersions;
161
162 const int pluginNamesSize = pluginNames.size();
163 const int pluginVersionsSize = pluginVersions.size();
164 for (int i = 0; i < pluginNamesSize; ++i) {
165 float pluginVersion = 1.0;
166 if (i < pluginVersionsSize)
167 pluginVersion = pluginVersions.at(i);
168 d->serverPlugins.insert(key: pluginNames.at(i), value: pluginVersion);
169 }
170
171 QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.begin();
172 for (; iter != d->plugins.end(); ++iter) {
173 const QString &pluginName = iter.key();
174 QQmlDebugClient::State newState = QQmlDebugClient::Unavailable;
175 if (d->serverPlugins.contains(key: pluginName))
176 newState = QQmlDebugClient::Enabled;
177
178 if (oldServerPlugins.contains(key: pluginName)
179 != d->serverPlugins.contains(key: pluginName)) {
180 iter.value()->stateChanged(state: newState);
181 }
182 }
183 } else {
184 qWarning() << "QQmlDebugConnection: Unknown control message id" << op;
185 }
186 } else {
187 QHash<QString, QQmlDebugClient *>::Iterator iter = d->plugins.find(key: name);
188 if (iter == d->plugins.end()) {
189 // We can get more messages for plugins we have removed because it takes time to
190 // send the advertisement message but the removal is instant locally.
191 if (!d->removedPlugins.contains(str: name)) {
192 qWarning() << "QQmlDebugConnection: Message received for missing plugin"
193 << name;
194 }
195 } else {
196 QQmlDebugClient *client = *iter;
197 QByteArray message;
198 while (!pack.atEnd()) {
199 pack >> message;
200 client->messageReceived(message);
201 }
202 }
203 }
204 }
205}
206
207void QQmlDebugConnection::handshakeTimeout()
208{
209 Q_D(QQmlDebugConnection);
210 if (!d->gotHello) {
211 qWarning() << "QQmlDebugConnection: Did not get handshake answer in time";
212 d->handshakeEventLoop.quit();
213 }
214}
215
216QQmlDebugConnection::QQmlDebugConnection(QObject *parent) :
217 QObject(*(new QQmlDebugConnectionPrivate), parent)
218{
219 Q_D(QQmlDebugConnection);
220 connect(sender: &d->handshakeTimer, signal: &QTimer::timeout, context: this, slot: &QQmlDebugConnection::handshakeTimeout);
221}
222
223QQmlDebugConnection::~QQmlDebugConnection()
224{
225 Q_D(QQmlDebugConnection);
226 QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
227 for (; iter != d->plugins.end(); ++iter)
228 emit iter.value()->stateChanged(state: QQmlDebugClient::NotConnected);
229}
230
231int QQmlDebugConnection::currentDataStreamVersion() const
232{
233 Q_D(const QQmlDebugConnection);
234 return d->currentDataStreamVersion;
235}
236
237void QQmlDebugConnection::setMaximumDataStreamVersion(int maximumVersion)
238{
239 Q_D(QQmlDebugConnection);
240 d->maximumDataStreamVersion = maximumVersion;
241}
242
243bool QQmlDebugConnection::isConnected() const
244{
245 Q_D(const QQmlDebugConnection);
246 return d->gotHello;
247}
248
249bool QQmlDebugConnection::isConnecting() const
250{
251 Q_D(const QQmlDebugConnection);
252 return !d->gotHello && d->device;
253}
254
255void QQmlDebugConnection::close()
256{
257 Q_D(QQmlDebugConnection);
258 if (d->gotHello) {
259 d->gotHello = false;
260 d->device->close();
261
262 QHash<QString, QQmlDebugClient*>::iterator iter = d->plugins.begin();
263 for (; iter != d->plugins.end(); ++iter)
264 emit iter.value()->stateChanged(state: QQmlDebugClient::NotConnected);
265 }
266
267 if (d->device) {
268 d->device->deleteLater();
269 d->device = nullptr;
270 }
271}
272
273bool QQmlDebugConnection::waitForConnected(int msecs)
274{
275 Q_D(QQmlDebugConnection);
276 auto socket = qobject_cast<QAbstractSocket*>(object: d->device);
277 if (!socket) {
278 if (!d->server || (!d->server->hasPendingConnections() &&
279 !d->server->waitForNewConnection(msec: msecs)))
280 return false;
281 } else if (!socket->waitForConnected(msecs)) {
282 return false;
283 }
284 // wait for handshake
285 d->handshakeTimer.start();
286 d->handshakeEventLoop.exec();
287 return d->gotHello;
288}
289
290QQmlDebugClient *QQmlDebugConnection::client(const QString &name) const
291{
292 Q_D(const QQmlDebugConnection);
293 return d->plugins.value(key: name, defaultValue: nullptr);
294}
295
296bool QQmlDebugConnection::addClient(const QString &name, QQmlDebugClient *client)
297{
298 Q_D(QQmlDebugConnection);
299 if (d->plugins.contains(key: name))
300 return false;
301 d->removedPlugins.removeAll(t: name);
302 d->plugins.insert(key: name, value: client);
303 d->advertisePlugins();
304 return true;
305}
306
307bool QQmlDebugConnection::removeClient(const QString &name)
308{
309 Q_D(QQmlDebugConnection);
310 if (!d->plugins.contains(key: name))
311 return false;
312 d->plugins.remove(key: name);
313 d->removedPlugins.append(t: name);
314 d->advertisePlugins();
315 return true;
316}
317
318float QQmlDebugConnection::serviceVersion(const QString &serviceName) const
319{
320 Q_D(const QQmlDebugConnection);
321 return d->serverPlugins.value(key: serviceName, defaultValue: -1);
322}
323
324bool QQmlDebugConnection::sendMessage(const QString &name, const QByteArray &message)
325{
326 Q_D(QQmlDebugConnection);
327 if (!isConnected() || !d->serverPlugins.contains(key: name))
328 return false;
329
330 QPacket pack(d->currentDataStreamVersion);
331 pack << name << message;
332 d->protocol->send(data: pack.data());
333 d->flush();
334
335 return true;
336}
337
338void QQmlDebugConnectionPrivate::flush()
339{
340 if (auto socket = qobject_cast<QAbstractSocket *>(object: device))
341 socket->flush();
342 else if (auto socket = qobject_cast<QLocalSocket *>(object: device))
343 socket->flush();
344}
345
346void QQmlDebugConnection::connectToHost(const QString &hostName, quint16 port)
347{
348 Q_D(QQmlDebugConnection);
349 if (d->gotHello)
350 close();
351 auto socket = new QTcpSocket(this);
352 d->device = socket;
353 d->createProtocol();
354 connect(sender: socket, signal: &QAbstractSocket::disconnected, context: this, slot: &QQmlDebugConnection::socketDisconnected);
355 connect(sender: socket, signal: &QAbstractSocket::connected, context: this, slot: &QQmlDebugConnection::socketConnected);
356 connect(sender: socket, signal: static_cast<void(QAbstractSocket::*)(QAbstractSocket::SocketError)>(
357 &QAbstractSocket::errorOccurred), context: this, slot: &QQmlDebugConnection::socketError);
358 connect(sender: socket, signal: &QAbstractSocket::stateChanged, context: this, slot: &QQmlDebugConnection::socketStateChanged);
359 socket->connectToHost(hostName, port);
360}
361
362void QQmlDebugConnection::startLocalServer(const QString &fileName)
363{
364 Q_D(QQmlDebugConnection);
365 if (d->gotHello)
366 close();
367 if (d->server)
368 d->server->deleteLater();
369 d->server = new QLocalServer(this);
370 // QueuedConnection so that waitForNewConnection() returns true.
371 connect(sender: d->server, signal: &QLocalServer::newConnection,
372 context: this, slot: &QQmlDebugConnection::newConnection, type: Qt::QueuedConnection);
373 d->server->listen(name: fileName);
374}
375
376class LocalSocketSignalTranslator : public QObject
377{
378 Q_OBJECT
379public:
380 LocalSocketSignalTranslator(QLocalSocket *parent) : QObject(parent)
381 {
382 connect(sender: parent, signal: &QLocalSocket::stateChanged,
383 context: this, slot: &LocalSocketSignalTranslator::onStateChanged);
384 connect(sender: parent, signal: &QLocalSocket::errorOccurred, context: this, slot: &LocalSocketSignalTranslator::onError);
385 }
386
387 void onError(QLocalSocket::LocalSocketError error)
388 {
389 emit socketError(static_cast<QAbstractSocket::SocketError>(error));
390 }
391
392 void onStateChanged(QLocalSocket::LocalSocketState state)
393 {
394 emit socketStateChanged(static_cast<QAbstractSocket::SocketState>(state));
395 }
396
397signals:
398 void socketError(QAbstractSocket::SocketError);
399 void socketStateChanged(QAbstractSocket::SocketState);
400};
401
402void QQmlDebugConnection::newConnection()
403{
404 Q_D(QQmlDebugConnection);
405 delete d->device;
406 QLocalSocket *socket = d->server->nextPendingConnection();
407 d->server->close();
408 d->device = socket;
409 d->createProtocol();
410 connect(sender: socket, signal: &QLocalSocket::disconnected, context: this, slot: &QQmlDebugConnection::socketDisconnected);
411 auto translator = new LocalSocketSignalTranslator(socket);
412
413 connect(sender: translator, signal: &LocalSocketSignalTranslator::socketError,
414 context: this, slot: &QQmlDebugConnection::socketError);
415 connect(sender: translator, signal: &LocalSocketSignalTranslator::socketStateChanged,
416 context: this, slot: &QQmlDebugConnection::socketStateChanged);
417 socketConnected();
418}
419
420void QQmlDebugConnectionPrivate::createProtocol()
421{
422 Q_Q(QQmlDebugConnection);
423 delete protocol;
424 protocol = new QPacketProtocol(device, q);
425 QObject::connect(sender: protocol, signal: &QPacketProtocol::readyRead,
426 context: q, slot: &QQmlDebugConnection::protocolReadyRead);
427}
428
429QT_END_NAMESPACE
430
431#include <qqmldebugconnection.moc>
432
433#include "moc_qqmldebugconnection_p.cpp"
434

source code of qtdeclarative/src/qmldebug/qqmldebugconnection.cpp