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