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 "qqmldebugserverfactory.h"
41
42#include <private/qqmldebugserver_p.h>
43#include <private/qqmldebugserverconnection_p.h>
44#include <private/qqmldebugservice_p.h>
45#include <private/qjsengine_p.h>
46#include <private/qqmlglobal_p.h>
47#include <private/qqmldebugpluginmanager_p.h>
48#include <private/qqmldebugserviceinterfaces_p.h>
49#include <private/qpacketprotocol_p.h>
50#include <private/qversionedpacket_p.h>
51
52#include <QtCore/QAtomicInt>
53#include <QtCore/QDir>
54#include <QtCore/QPluginLoader>
55#include <QtCore/QStringList>
56#include <QtCore/QVector>
57#include <QtCore/qwaitcondition.h>
58
59QT_BEGIN_NAMESPACE
60
61/*
62 QQmlDebug Protocol (Version 1):
63
64 handshake:
65 1. Client sends
66 "QDeclarativeDebugServer" 0 version pluginNames [QDataStream version]
67 version: an int representing the highest protocol version the client knows
68 pluginNames: plugins available on client side
69 2. Server sends
70 "QDeclarativeDebugClient" 0 version pluginNames pluginVersions [QDataStream version]
71 version: an int representing the highest protocol version the client & server know
72 pluginNames: plugins available on server side. plugins both in the client and server message are enabled.
73 client plugin advertisement
74 1. Client sends
75 "QDeclarativeDebugServer" 1 pluginNames
76 server plugin advertisement (not implemented: all services are required to register before open())
77 1. Server sends
78 "QDeclarativeDebugClient" 1 pluginNames pluginVersions
79 plugin communication:
80 Everything send with a header different to "QDeclarativeDebugServer" is sent to the appropriate plugin.
81 */
82
83Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection)
84
85const int protocolVersion = 1;
86using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
87
88class QQmlDebugServerImpl;
89class QQmlDebugServerThread : public QThread
90{
91public:
92 QQmlDebugServerThread() : m_server(nullptr), m_portFrom(-1), m_portTo(-1) {}
93
94 void setServer(QQmlDebugServerImpl *server)
95 {
96 m_server = server;
97 }
98
99 void setPortRange(int portFrom, int portTo, const QString &hostAddress)
100 {
101 m_pluginName = QLatin1String("QTcpServerConnection");
102 m_portFrom = portFrom;
103 m_portTo = portTo;
104 m_hostAddress = hostAddress;
105 }
106
107 void setFileName(const QString &fileName)
108 {
109 m_pluginName = QLatin1String("QLocalClientConnection");
110 m_fileName = fileName;
111 }
112
113 const QString &pluginName() const
114 {
115 return m_pluginName;
116 }
117
118 void run() override;
119
120private:
121 QQmlDebugServerImpl *m_server;
122 QString m_pluginName;
123 int m_portFrom;
124 int m_portTo;
125 QString m_hostAddress;
126 QString m_fileName;
127};
128
129class QQmlDebugServerImpl : public QQmlDebugServer
130{
131 Q_OBJECT
132public:
133 QQmlDebugServerImpl();
134
135 bool blockingMode() const override;
136
137 QQmlDebugService *service(const QString &name) const override;
138
139 void addEngine(QJSEngine *engine) override;
140 void removeEngine(QJSEngine *engine) override;
141 bool hasEngine(QJSEngine *engine) const override;
142
143 bool addService(const QString &name, QQmlDebugService *service) override;
144 bool removeService(const QString &name) override;
145
146 bool open(const QVariantHash &configuration) override;
147 void setDevice(QIODevice *socket) override;
148
149 void parseArguments();
150
151 static void cleanup();
152
153private:
154 friend class QQmlDebugServerThread;
155 friend class QQmlDebugServerFactory;
156
157 class EngineCondition {
158 public:
159 EngineCondition() : numServices(0), condition(new QWaitCondition) {}
160
161 bool waitForServices(QMutex *locked, int numEngines);
162 bool isWaiting() const { return numServices > 0; }
163
164 void wake();
165 private:
166 int numServices;
167
168 // shared pointer to allow for QHash-inflicted copying.
169 QSharedPointer<QWaitCondition> condition;
170 };
171
172 bool canSendMessage(const QString &name);
173 void doSendMessage(const QString &name, const QByteArray &message);
174 void wakeEngine(QJSEngine *engine);
175 void sendMessage(const QString &name, const QByteArray &message);
176 void sendMessages(const QString &name, const QList<QByteArray> &messages);
177 void changeServiceState(const QString &serviceName, QQmlDebugService::State state);
178 void removeThread();
179 void receiveMessage();
180 void protocolError();
181
182 QQmlDebugServerConnection *m_connection;
183 QHash<QString, QQmlDebugService *> m_plugins;
184 QStringList m_clientPlugins;
185 bool m_gotHello;
186 bool m_blockingMode;
187
188 QHash<QJSEngine *, EngineCondition> m_engineConditions;
189
190 mutable QMutex m_helloMutex;
191 QWaitCondition m_helloCondition;
192 QQmlDebugServerThread m_thread;
193 QPacketProtocol *m_protocol;
194 QAtomicInt m_changeServiceStateCalls;
195};
196
197void QQmlDebugServerImpl::cleanup()
198{
199 QQmlDebugServerImpl *server = static_cast<QQmlDebugServerImpl *>(
200 QQmlDebugConnector::instance());
201 if (!server)
202 return;
203
204 {
205 QObject signalSource;
206 for (QHash<QString, QQmlDebugService *>::ConstIterator i = server->m_plugins.constBegin();
207 i != server->m_plugins.constEnd(); ++i) {
208 server->m_changeServiceStateCalls.ref();
209 QString key = i.key();
210 // Process this in the server's thread.
211 connect(sender: &signalSource, signal: &QObject::destroyed, context: server, slot: [key, server](){
212 server->changeServiceState(serviceName: key, state: QQmlDebugService::NotConnected);
213 }, type: Qt::QueuedConnection);
214 }
215 }
216
217 // Wait for changeServiceState calls to finish
218 // (while running an event loop because some services
219 // might again defer execution of stuff in the GUI thread)
220 QEventLoop loop;
221 while (!server->m_changeServiceStateCalls.testAndSetOrdered(expectedValue: 0, newValue: 0))
222 loop.processEvents();
223
224 // Stop the thread while the application is still there.
225 server->m_thread.exit();
226 server->m_thread.wait();
227}
228
229void QQmlDebugServerThread::run()
230{
231 Q_ASSERT_X(m_server != nullptr, Q_FUNC_INFO, "There should always be a debug server available here.");
232 QQmlDebugServerConnection *connection = loadQQmlDebugServerConnection(key: m_pluginName);
233 if (connection) {
234 {
235 QMutexLocker connectionLocker(&m_server->m_helloMutex);
236 m_server->m_connection = connection;
237 connection->setServer(m_server);
238 m_server->m_helloCondition.wakeAll();
239 }
240
241 if (m_fileName.isEmpty()) {
242 if (!connection->setPortRange(portFrom: m_portFrom, portTo: m_portTo, block: m_server->blockingMode(),
243 hostaddress: m_hostAddress))
244 return;
245 } else if (!connection->setFileName(fileName: m_fileName, block: m_server->blockingMode())) {
246 return;
247 }
248
249 if (m_server->blockingMode())
250 connection->waitForConnection();
251 } else {
252 qWarning() << "QML Debugger: Couldn't load plugin" << m_pluginName;
253 return;
254 }
255
256 exec();
257
258 // make sure events still waiting are processed
259 QEventLoop eventLoop;
260 eventLoop.processEvents(flags: QEventLoop::AllEvents);
261}
262
263bool QQmlDebugServerImpl::blockingMode() const
264{
265 return m_blockingMode;
266}
267
268static void cleanupOnShutdown()
269{
270 // We cannot do this in the destructor as the connection plugin will get unloaded before the
271 // server plugin and we need the connection to send any remaining data. This function is
272 // triggered before any plugins are unloaded.
273 QQmlDebugServerImpl::cleanup();
274}
275
276QQmlDebugServerImpl::QQmlDebugServerImpl() :
277 m_connection(nullptr),
278 m_gotHello(false),
279 m_blockingMode(false)
280{
281 static bool postRoutineAdded = false;
282 if (!postRoutineAdded) {
283 qAddPostRoutine(cleanupOnShutdown);
284 postRoutineAdded = true;
285 }
286
287 // used in sendMessages
288 qRegisterMetaType<QList<QByteArray> >(typeName: "QList<QByteArray>");
289 // used in changeServiceState
290 qRegisterMetaType<QQmlDebugService::State>(typeName: "QQmlDebugService::State");
291
292 m_thread.setServer(this);
293 moveToThread(thread: &m_thread);
294
295 // Remove the thread immmediately when it finishes, so that we don't have to wait for the
296 // event loop to signal that.
297 QObject::connect(sender: &m_thread, signal: &QThread::finished, receiver: this, slot: &QQmlDebugServerImpl::removeThread,
298 type: Qt::DirectConnection);
299 m_thread.setObjectName(QStringLiteral("QQmlDebugServerThread"));
300 parseArguments();
301}
302
303bool QQmlDebugServerImpl::open(const QVariantHash &configuration = QVariantHash())
304{
305 if (m_thread.isRunning())
306 return false;
307 if (!configuration.isEmpty()) {
308 m_blockingMode = configuration[QLatin1String("block")].toBool();
309 if (configuration.contains(key: QLatin1String("portFrom"))) {
310 int portFrom = configuration[QLatin1String("portFrom")].toInt();
311 int portTo = configuration[QLatin1String("portTo")].toInt();
312 m_thread.setPortRange(portFrom, portTo: portTo == -1 ? portFrom : portTo,
313 hostAddress: configuration[QLatin1String("hostAddress")].toString());
314 } else if (configuration.contains(key: QLatin1String("fileName"))) {
315 m_thread.setFileName(configuration[QLatin1String("fileName")].toString());
316 } else {
317 return false;
318 }
319 }
320
321 if (m_thread.pluginName().isEmpty())
322 return false;
323
324 QMutexLocker locker(&m_helloMutex);
325 m_thread.start();
326 m_helloCondition.wait(lockedMutex: &m_helloMutex); // wait for connection
327 if (m_blockingMode && !m_gotHello)
328 m_helloCondition.wait(lockedMutex: &m_helloMutex); // wait for hello
329 return true;
330}
331
332void QQmlDebugServerImpl::parseArguments()
333{
334 // format: qmljsdebugger=port:<port_from>[,port_to],host:<ip address>][,block]
335 const QString args = commandLineArguments();
336 if (args.isEmpty())
337 return; // Manual initialization, through QQmlDebugServer::open()
338
339 // ### remove port definition when protocol is changed
340 int portFrom = 0;
341 int portTo = 0;
342 bool block = false;
343 bool ok = false;
344 QString hostAddress;
345 QString fileName;
346 QStringList services;
347
348 const auto lstjsDebugArguments = args.splitRef(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
349 for (auto argsIt = lstjsDebugArguments.begin(), argsItEnd = lstjsDebugArguments.end(); argsIt != argsItEnd; ++argsIt) {
350 const QStringRef &strArgument = *argsIt;
351 if (strArgument.startsWith(s: QLatin1String("port:"))) {
352 portFrom = strArgument.mid(pos: 5).toInt(ok: &ok);
353 portTo = portFrom;
354 const auto argsNext = argsIt + 1;
355 if (argsNext == argsItEnd)
356 break;
357 if (ok) {
358 portTo = argsNext->toString().toInt(ok: &ok);
359 if (ok) {
360 ++argsIt;
361 } else {
362 portTo = portFrom;
363 ok = true;
364 }
365 }
366 } else if (strArgument.startsWith(s: QLatin1String("host:"))) {
367 hostAddress = strArgument.mid(pos: 5).toString();
368 } else if (strArgument == QLatin1String("block")) {
369 block = true;
370 } else if (strArgument.startsWith(s: QLatin1String("file:"))) {
371 fileName = strArgument.mid(pos: 5).toString();
372 ok = !fileName.isEmpty();
373 } else if (strArgument.startsWith(s: QLatin1String("services:"))) {
374 services.append(t: strArgument.mid(pos: 9).toString());
375 } else if (!services.isEmpty()) {
376 services.append(t: strArgument.toString());
377 } else if (!strArgument.startsWith(s: QLatin1String("connector:"))) {
378 const QString message = tr(s: "QML Debugger: Invalid argument \"%1\" detected."
379 " Ignoring the same.").arg(a: strArgument.toString());
380 qWarning(msg: "%s", qPrintable(message));
381 }
382 }
383
384 if (ok) {
385 setServices(services);
386 m_blockingMode = block;
387 if (!fileName.isEmpty())
388 m_thread.setFileName(fileName);
389 else
390 m_thread.setPortRange(portFrom, portTo, hostAddress);
391 } else {
392 QString usage;
393 QTextStream str(&usage);
394 str << tr(s: "QML Debugger: Ignoring \"-qmljsdebugger=%1\".").arg(a: args) << '\n'
395 << tr(s: "The format is \"-qmljsdebugger=[file:<file>|port:<port_from>][,<port_to>]"
396 "[,host:<ip address>][,block][,services:<service>][,<service>]*\"") << '\n'
397 << tr(s: "\"file:\" can be used to specify the name of a file the debugger will try "
398 "to connect to using a QLocalSocket. If \"file:\" is given any \"host:\" and"
399 "\"port:\" arguments will be ignored.") << '\n'
400 << tr(s: "\"host:\" and \"port:\" can be used to specify an address and a single "
401 "port or a range of ports the debugger will try to bind to with a "
402 "QTcpServer.") << '\n'
403 << tr(s: "\"block\" makes the debugger and some services wait for clients to be "
404 "connected and ready before the first QML engine starts.") << '\n'
405 << tr(s: "\"services:\" can be used to specify which debug services the debugger "
406 "should load. Some debug services interact badly with others. The V4 "
407 "debugger should not be loaded when using the QML profiler as it will force "
408 "any V4 engines to use the JavaScript interpreter rather than the JIT. The "
409 "following debug services are available by default:") << '\n'
410 << QQmlEngineDebugService::s_key << "\t- " << tr(s: "The QML debugger") << '\n'
411 << QV4DebugService::s_key << "\t- " << tr(s: "The V4 debugger") << '\n'
412 << QQmlInspectorService::s_key << "\t- " << tr(s: "The QML inspector") << '\n'
413 << QQmlProfilerService::s_key << "\t- " << tr(s: "The QML profiler") << '\n'
414 << QQmlEngineControlService::s_key << "\t- "
415 //: Please preserve the line breaks and formatting
416 << tr(s: "Allows the client to delay the starting and stopping of\n"
417 "\t\t QML engines until other services are ready. QtCreator\n"
418 "\t\t uses this service with the QML profiler in order to\n"
419 "\t\t profile multiple QML engines at the same time.")
420 << '\n' << QDebugMessageService::s_key << "\t- "
421 //: Please preserve the line breaks and formatting
422 << tr(s: "Sends qDebug() and similar messages over the QML debug\n"
423 "\t\t connection. QtCreator uses this for showing debug\n"
424 "\t\t messages in the debugger console.") << '\n'
425 << '\n' << QQmlDebugTranslationService::s_key << "\t- "
426 //: Please preserve the line breaks and formatting
427 << tr(s: "helps to see if a translated text\n"
428 "\t\t will result in an elided text\n"
429 "\t\t in QML elements.") << '\n'
430 << tr(s: "Other services offered by qmltooling plugins that implement "
431 "QQmlDebugServiceFactory and which can be found in the standard plugin "
432 "paths will also be available and can be specified. If no \"services\" "
433 "argument is given, all services found this way, including the default "
434 "ones, are loaded.");
435 qWarning(msg: "%s", qPrintable(usage));
436 }
437}
438
439void QQmlDebugServerImpl::receiveMessage()
440{
441 typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt;
442
443 // to be executed in debugger thread
444 Q_ASSERT(QThread::currentThread() == thread());
445
446 if (!m_protocol)
447 return;
448
449 QQmlDebugPacket in(m_protocol->read());
450
451 QString name;
452
453 in >> name;
454 if (name == QLatin1String("QDeclarativeDebugServer")) {
455 int op = -1;
456 in >> op;
457 if (op == 0) {
458 int version;
459 in >> version >> m_clientPlugins;
460
461 //Get the supported QDataStream version
462 if (!in.atEnd()) {
463 in >> s_dataStreamVersion;
464 if (s_dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion)
465 s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
466 }
467
468 bool clientSupportsMultiPackets = false;
469 if (!in.atEnd())
470 in >> clientSupportsMultiPackets;
471
472 // Send the hello answer immediately, since it needs to arrive before
473 // the plugins below start sending messages.
474
475 QQmlDebugPacket out;
476 QStringList pluginNames;
477 QList<float> pluginVersions;
478 if (clientSupportsMultiPackets) { // otherwise, disable all plugins
479 const int count = m_plugins.count();
480 pluginNames.reserve(size: count);
481 pluginVersions.reserve(size: count);
482 for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin();
483 i != m_plugins.constEnd(); ++i) {
484 pluginNames << i.key();
485 pluginVersions << i.value()->version();
486 }
487 }
488
489 out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion
490 << pluginNames << pluginVersions << dataStreamVersion();
491
492 m_protocol->send(data: out.data());
493 m_connection->flush();
494
495 QMutexLocker helloLock(&m_helloMutex);
496 m_gotHello = true;
497
498 for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
499 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
500 if (m_clientPlugins.contains(str: iter.key()))
501 newState = QQmlDebugService::Enabled;
502 m_changeServiceStateCalls.ref();
503 changeServiceState(serviceName: iter.key(), state: newState);
504 }
505
506 m_helloCondition.wakeAll();
507
508 } else if (op == 1) {
509 // Service Discovery
510 QStringList oldClientPlugins = m_clientPlugins;
511 in >> m_clientPlugins;
512
513 for (DebugServiceConstIt iter = m_plugins.constBegin(), cend = m_plugins.constEnd(); iter != cend; ++iter) {
514 const QString &pluginName = iter.key();
515 QQmlDebugService::State newState = QQmlDebugService::Unavailable;
516 if (m_clientPlugins.contains(str: pluginName))
517 newState = QQmlDebugService::Enabled;
518
519 if (oldClientPlugins.contains(str: pluginName)
520 != m_clientPlugins.contains(str: pluginName)) {
521 m_changeServiceStateCalls.ref();
522 changeServiceState(serviceName: iter.key(), state: newState);
523 }
524 }
525
526 } else {
527 qWarning(msg: "QML Debugger: Invalid control message %d.", op);
528 protocolError();
529 return;
530 }
531
532 } else {
533 if (m_gotHello) {
534 QHash<QString, QQmlDebugService *>::Iterator iter = m_plugins.find(key: name);
535 if (iter == m_plugins.end()) {
536 qWarning() << "QML Debugger: Message received for missing plugin" << name << '.';
537 } else {
538 QQmlDebugService *service = *iter;
539 QByteArray message;
540 while (!in.atEnd()) {
541 in >> message;
542 service->messageReceived(message);
543 }
544 }
545 } else {
546 qWarning(msg: "QML Debugger: Invalid hello message.");
547 }
548
549 }
550}
551
552void QQmlDebugServerImpl::changeServiceState(const QString &serviceName,
553 QQmlDebugService::State newState)
554{
555 // to be executed in debugger thread
556 Q_ASSERT(QThread::currentThread() == thread());
557
558 QQmlDebugService *service = m_plugins.value(key: serviceName);
559 if (service && service->state() != newState) {
560 service->stateAboutToBeChanged(newState);
561 service->setState(newState);
562 service->stateChanged(newState);
563 }
564
565 m_changeServiceStateCalls.deref();
566}
567
568void QQmlDebugServerImpl::removeThread()
569{
570 Q_ASSERT(m_thread.isFinished());
571 Q_ASSERT(QThread::currentThread() == thread());
572
573 QThread *parentThread = m_thread.thread();
574
575 delete m_connection;
576 m_connection = nullptr;
577
578 // Move it back to the parent thread so that we can potentially restart it on a new thread.
579 moveToThread(thread: parentThread);
580}
581
582QQmlDebugService *QQmlDebugServerImpl::service(const QString &name) const
583{
584 return m_plugins.value(key: name);
585}
586
587void QQmlDebugServerImpl::addEngine(QJSEngine *engine)
588{
589 // to be executed outside of debugger thread
590 Q_ASSERT(QThread::currentThread() != &m_thread);
591
592 QMutexLocker locker(&m_helloMutex);
593 Q_ASSERT(!m_engineConditions.contains(engine));
594
595 for (QQmlDebugService *service : qAsConst(t&: m_plugins))
596 service->engineAboutToBeAdded(engine);
597
598 m_engineConditions[engine].waitForServices(locked: &m_helloMutex, numEngines: m_plugins.count());
599
600 for (QQmlDebugService *service : qAsConst(t&: m_plugins))
601 service->engineAdded(engine);
602}
603
604void QQmlDebugServerImpl::removeEngine(QJSEngine *engine)
605{
606 // to be executed outside of debugger thread
607 Q_ASSERT(QThread::currentThread() != &m_thread);
608
609 QMutexLocker locker(&m_helloMutex);
610 Q_ASSERT(m_engineConditions.contains(engine));
611
612 for (QQmlDebugService *service : qAsConst(t&: m_plugins))
613 service->engineAboutToBeRemoved(engine);
614
615 m_engineConditions[engine].waitForServices(locked: &m_helloMutex, numEngines: m_plugins.count());
616
617 for (QQmlDebugService *service : qAsConst(t&: m_plugins))
618 service->engineRemoved(engine);
619
620 m_engineConditions.remove(key: engine);
621}
622
623bool QQmlDebugServerImpl::hasEngine(QJSEngine *engine) const
624{
625 QMutexLocker locker(&m_helloMutex);
626 QHash<QJSEngine *, EngineCondition>::ConstIterator i = m_engineConditions.constFind(key: engine);
627 // if we're still waiting the engine isn't fully "there", yet, nor fully removed.
628 return i != m_engineConditions.constEnd() && !i.value().isWaiting();
629}
630
631bool QQmlDebugServerImpl::addService(const QString &name, QQmlDebugService *service)
632{
633 // to be executed before thread starts
634 Q_ASSERT(!m_thread.isRunning());
635
636 if (!service || m_plugins.contains(key: name))
637 return false;
638
639 connect(sender: service, signal: &QQmlDebugService::messageToClient,
640 receiver: this, slot: &QQmlDebugServerImpl::sendMessage);
641 connect(sender: service, signal: &QQmlDebugService::messagesToClient,
642 receiver: this, slot: &QQmlDebugServerImpl::sendMessages);
643
644 connect(sender: service, signal: &QQmlDebugService::attachedToEngine,
645 receiver: this, slot: &QQmlDebugServerImpl::wakeEngine, type: Qt::QueuedConnection);
646 connect(sender: service, signal: &QQmlDebugService::detachedFromEngine,
647 receiver: this, slot: &QQmlDebugServerImpl::wakeEngine, type: Qt::QueuedConnection);
648
649 service->setState(QQmlDebugService::Unavailable);
650 m_plugins.insert(key: name, value: service);
651
652 return true;
653}
654
655bool QQmlDebugServerImpl::removeService(const QString &name)
656{
657 // to be executed after thread ends
658 Q_ASSERT(!m_thread.isRunning());
659
660 QQmlDebugService *service = m_plugins.value(key: name);
661 if (!service)
662 return false;
663
664 m_plugins.remove(key: name);
665 service->setState(QQmlDebugService::NotConnected);
666
667 disconnect(sender: service, signal: &QQmlDebugService::detachedFromEngine,
668 receiver: this, slot: &QQmlDebugServerImpl::wakeEngine);
669 disconnect(sender: service, signal: &QQmlDebugService::attachedToEngine,
670 receiver: this, slot: &QQmlDebugServerImpl::wakeEngine);
671
672 disconnect(sender: service, signal: &QQmlDebugService::messagesToClient,
673 receiver: this, slot: &QQmlDebugServerImpl::sendMessages);
674 disconnect(sender: service, signal: &QQmlDebugService::messageToClient,
675 receiver: this, slot: &QQmlDebugServerImpl::sendMessage);
676
677 return true;
678}
679
680bool QQmlDebugServerImpl::canSendMessage(const QString &name)
681{
682 // to be executed in debugger thread
683 Q_ASSERT(QThread::currentThread() == thread());
684 return m_connection && m_connection->isConnected() && m_protocol &&
685 m_clientPlugins.contains(str: name);
686}
687
688void QQmlDebugServerImpl::doSendMessage(const QString &name, const QByteArray &message)
689{
690 QQmlDebugPacket out;
691 out << name << message;
692 m_protocol->send(data: out.data());
693}
694
695void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &message)
696{
697 if (canSendMessage(name)) {
698 doSendMessage(name, message);
699 m_connection->flush();
700 }
701}
702
703void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages)
704{
705 if (canSendMessage(name)) {
706 QQmlDebugPacket out;
707 out << name;
708 for (const QByteArray &message : messages)
709 out << message;
710 m_protocol->send(data: out.data());
711 m_connection->flush();
712 }
713}
714
715void QQmlDebugServerImpl::wakeEngine(QJSEngine *engine)
716{
717 // to be executed in debugger thread
718 Q_ASSERT(QThread::currentThread() == thread());
719
720 QMutexLocker locker(&m_helloMutex);
721 m_engineConditions[engine].wake();
722}
723
724bool QQmlDebugServerImpl::EngineCondition::waitForServices(QMutex *locked, int num)
725{
726 Q_ASSERT_X(numServices == 0, Q_FUNC_INFO, "Request to wait again before previous wait finished");
727 numServices = num;
728 return numServices > 0 ? condition->wait(lockedMutex: locked) : true;
729}
730
731void QQmlDebugServerImpl::EngineCondition::wake()
732{
733 if (--numServices == 0)
734 condition->wakeAll();
735 Q_ASSERT_X(numServices >=0, Q_FUNC_INFO, "Woken more often than #services.");
736}
737
738void QQmlDebugServerImpl::setDevice(QIODevice *socket)
739{
740 m_protocol = new QPacketProtocol(socket, this);
741 QObject::connect(sender: m_protocol, signal: &QPacketProtocol::readyRead,
742 receiver: this, slot: &QQmlDebugServerImpl::receiveMessage);
743 QObject::connect(sender: m_protocol, signal: &QPacketProtocol::error,
744 receiver: this, slot: &QQmlDebugServerImpl::protocolError);
745
746 if (blockingMode())
747 m_protocol->waitForReadyRead(msecs: -1);
748}
749
750void QQmlDebugServerImpl::protocolError()
751{
752 qWarning(msg: "QML Debugger: A protocol error has occurred! Giving up ...");
753 m_connection->disconnect();
754 // protocol might still be processing packages at this point
755 m_protocol->deleteLater();
756 m_protocol = nullptr;
757}
758
759QQmlDebugConnector *QQmlDebugServerFactory::create(const QString &key)
760{
761 // Cannot parent it to this because it gets moved to another thread
762 return (key == QLatin1String("QQmlDebugServer") ? new QQmlDebugServerImpl : nullptr);
763}
764
765QT_END_NAMESPACE
766
767#include "qqmldebugserver.moc"
768

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp