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

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