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

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