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>
16#include <QtCore/QAtomicInt>
18#include <QtCore/QPluginLoader>
19#include <QtCore/QStringList>
20#include <QtCore/QVector>
21#include <QtCore/qwaitcondition.h>
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
47Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection)
49const int protocolVersion = 1;
50using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
63 void setPortRange(
int portFrom,
int portTo,
const QString &hostAddress)
65 m_pluginName = QLatin1String(
"QTcpServerConnection");
66 m_portFrom = portFrom;
68 m_hostAddress = hostAddress;
73 m_pluginName = QLatin1String(
"QLocalClientConnection");
74 m_fileName = fileName;
89 QString m_hostAddress;
107 bool addService(
const QString &name, QQmlDebugService *service)
override;
110 bool open(
const QVariantHash &configuration)
override;
119 friend class QQmlDebugServerFactory;
121 class EngineCondition {
123 EngineCondition() : numServices(0), condition(
new QWaitCondition) {}
125 bool waitForServices(QMutex *locked,
int numEngines);
126 bool isWaiting()
const {
return numServices > 0; }
133 QSharedPointer<QWaitCondition> condition;
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);
143 void receiveMessage();
144 void protocolError();
146 QQmlDebugServerConnection *m_connection;
147 QHash<QString, QQmlDebugService *> m_plugins;
148 QStringList m_clientPlugins;
152 QHash<QJSEngine *, EngineCondition> m_engineConditions;
154 mutable QMutex m_helloMutex;
155 QWaitCondition m_helloCondition;
157 QPacketProtocol *m_protocol;
158 QAtomicInt m_changeServiceStateCalls;
164 QQmlDebugConnector::instance());
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();
175 connect(&signalSource, &QObject::destroyed, server, [key, server](){
176 server->changeServiceState(key, QQmlDebugService::NotConnected);
177 }, Qt::QueuedConnection);
185 while (!server->m_changeServiceStateCalls.testAndSetOrdered(0, 0))
186 loop.processEvents();
189 server->m_thread.exit();
190 server->m_thread.wait();
195 Q_ASSERT_X(m_server !=
nullptr, Q_FUNC_INFO,
"There should always be a debug server available here.");
196 QQmlDebugServerConnection *connection = loadQQmlDebugServerConnection(m_pluginName);
199 QMutexLocker connectionLocker(&m_server->m_helloMutex);
200 m_server->m_connection = connection;
201 connection->setServer(m_server);
202 m_server->m_helloCondition.wakeAll();
205 if (m_fileName.isEmpty()) {
206 if (!connection->setPortRange(m_portFrom, m_portTo, m_server->blockingMode(),
209 }
else if (!connection->setFileName(m_fileName, m_server->blockingMode())) {
214 connection->waitForConnection();
216 qWarning() <<
"QML Debugger: Couldn't load plugin" << m_pluginName;
223 QEventLoop eventLoop;
224 eventLoop.processEvents(QEventLoop::AllEvents);
229 return m_blockingMode;
241 m_connection(
nullptr),
243 m_blockingMode(
false)
245 static bool postRoutineAdded =
false;
246 if (!postRoutineAdded) {
248 postRoutineAdded =
true;
252 qRegisterMetaType<QList<QByteArray> >(
"QList<QByteArray>");
254 qRegisterMetaType<QQmlDebugService::State>(
"QQmlDebugService::State");
256 m_thread.setServer(
this);
257 moveToThread(&m_thread);
261 QObject::connect(&m_thread, &QThread::finished,
this, &QQmlDebugServerImpl::removeThread,
262 Qt::DirectConnection);
263 m_thread.setObjectName(QStringLiteral(
"QQmlDebugServerThread"));
269 if (m_thread.isRunning())
271 if (!configuration.isEmpty()) {
272 m_blockingMode = configuration[QLatin1String(
"block")].toBool();
273 if (configuration.contains(QLatin1String(
"portFrom"))) {
274 int portFrom = configuration[QLatin1String(
"portFrom")].toInt();
275 int portTo = configuration[QLatin1String(
"portTo")].toInt();
276 m_thread.setPortRange(portFrom, portTo == -1 ? portFrom : portTo,
277 configuration[QLatin1String(
"hostAddress")].toString());
278 }
else if (configuration.contains(QLatin1String(
"fileName"))) {
279 m_thread.setFileName(configuration[QLatin1String(
"fileName")].toString());
285 if (m_thread.pluginName().isEmpty())
288 QMutexLocker locker(&m_helloMutex);
290 m_helloCondition.wait(&m_helloMutex);
291 if (m_blockingMode && !m_gotHello)
292 m_helloCondition.wait(&m_helloMutex);
299 const QString args = commandLineArguments();
310 QStringList services;
312 const auto lstjsDebugArguments = QStringView{args}.split(QLatin1Char(
','), Qt::SkipEmptyParts);
313 for (
auto argsIt = lstjsDebugArguments.begin(), argsItEnd = lstjsDebugArguments.end(); argsIt != argsItEnd; ++argsIt) {
314 const QStringView &strArgument = *argsIt;
315 if (strArgument.startsWith(QLatin1String(
"port:"))) {
316 portFrom = strArgument.mid(5).toInt(&ok);
318 const auto argsNext = argsIt + 1;
319 if (argsNext == argsItEnd)
322 portTo = argsNext->toString().toInt(&ok);
330 }
else if (strArgument.startsWith(QLatin1String(
"host:"))) {
331 hostAddress = strArgument.mid(5).toString();
332 }
else if (strArgument == QLatin1String(
"block")) {
334 }
else if (strArgument.startsWith(QLatin1String(
"file:"))) {
335 fileName = strArgument.mid(5).toString();
336 ok = !fileName.isEmpty();
337 }
else if (strArgument.startsWith(QLatin1String(
"services:"))) {
338 services.append(strArgument.mid(9).toString());
339 }
else if (!services.isEmpty()) {
340 services.append(strArgument.toString());
341 }
else if (!strArgument.startsWith(QLatin1String(
"connector:"))) {
342 const QString message = tr(
"QML Debugger: Invalid argument \"%1\" detected."
343 " Ignoring the same.").arg(strArgument.toString());
344 qWarning(
"%s", qPrintable(message));
349 setServices(services);
350 m_blockingMode = block;
351 if (!fileName.isEmpty())
352 m_thread.setFileName(fileName);
354 m_thread.setPortRange(portFrom, portTo, hostAddress);
357 QTextStream str(&usage);
358 str << tr(
"QML Debugger: Ignoring \"-qmljsdebugger=%1\".").arg(args) <<
'\n'
359 << tr(
"The format is \"-qmljsdebugger=[file:<file>|port:<port_from>][,<port_to>]"
360 "[,host:<ip address>][,block][,services:<service>][,<service>]*\"") <<
'\n'
361 << tr(
"\"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(
"\"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(
"\"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(
"\"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(
"The QML debugger") <<
'\n'
375 << QV4DebugService::s_key <<
"\t- " << tr(
"The V4 debugger") <<
'\n'
376 << QQmlInspectorService::s_key <<
"\t- " << tr(
"The QML inspector") <<
'\n'
377 << QQmlProfilerService::s_key <<
"\t- " << tr(
"The QML profiler") <<
'\n'
378 << QQmlEngineControlService::s_key <<
"\t- "
380 << tr(
"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- "
386 << tr(
"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- "
392 << tr(
"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'
396 << tr(
"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(
"%s", qPrintable(usage));
407 typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt;
410 Q_ASSERT(QThread::currentThread() == thread());
415 QQmlDebugPacket in(m_protocol->read());
420 if (name == QLatin1String(
"QDeclarativeDebugServer")) {
425 in >> version >> m_clientPlugins;
429 in >> s_dataStreamVersion;
430 if (s_dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion)
431 s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
434 bool clientSupportsMultiPackets =
false;
436 in >> clientSupportsMultiPackets;
442 QStringList pluginNames;
443 QList<
float> pluginVersions;
444 if (clientSupportsMultiPackets) {
445 const int count = m_plugins.size();
446 pluginNames.reserve(count);
447 pluginVersions.reserve(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();
455 out << QString(QStringLiteral(
"QDeclarativeDebugClient")) << 0 << protocolVersion
456 << pluginNames << pluginVersions << dataStreamVersion();
458 m_protocol->send(out.data());
459 m_connection->flush();
461 QMutexLocker helloLock(&m_helloMutex);
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(iter.key()))
467 newState = QQmlDebugService::Enabled;
468 m_changeServiceStateCalls.ref();
469 changeServiceState(iter.key(), newState);
472 m_helloCondition.wakeAll();
474 }
else if (op == 1) {
476 QStringList oldClientPlugins = m_clientPlugins;
477 in >> m_clientPlugins;
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(pluginName))
483 newState = QQmlDebugService::Enabled;
485 if (oldClientPlugins.contains(pluginName)
486 != m_clientPlugins.contains(pluginName)) {
487 m_changeServiceStateCalls.ref();
488 changeServiceState(iter.key(), newState);
493 qWarning(
"QML Debugger: Invalid control message %d.", op);
500 const auto iter = m_plugins.constFind(name);
501 if (iter == m_plugins.cend()) {
502 qWarning() <<
"QML Debugger: Message received for missing plugin" << name <<
'.';
504 QQmlDebugService *service = *iter;
506 while (!in.atEnd()) {
508 service->messageReceived(message);
512 qWarning(
"QML Debugger: Invalid hello message.");
519 QQmlDebugService::State newState)
522 Q_ASSERT(QThread::currentThread() == thread());
524 QQmlDebugService *service = m_plugins.value(serviceName);
525 if (service && service->state() != newState) {
526 service->stateAboutToBeChanged(newState);
527 service->setState(newState);
528 service->stateChanged(newState);
531 m_changeServiceStateCalls.deref();
536 Q_ASSERT(m_thread.isFinished());
537 Q_ASSERT(QThread::currentThread() == thread());
539 QThread *parentThread = m_thread.thread();
542 m_connection =
nullptr;
545 moveToThread(parentThread);
550 return m_plugins.value(name);
556 Q_ASSERT(QThread::currentThread() != &m_thread);
558 QMutexLocker locker(&m_helloMutex);
559 Q_ASSERT(!m_engineConditions.contains(engine));
561 for (QQmlDebugService *service : std::as_const(m_plugins))
562 service->engineAboutToBeAdded(engine);
564 m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
566 for (QQmlDebugService *service : std::as_const(m_plugins))
567 service->engineAdded(engine);
573 Q_ASSERT(QThread::currentThread() != &m_thread);
575 QMutexLocker locker(&m_helloMutex);
576 Q_ASSERT(m_engineConditions.contains(engine));
578 for (QQmlDebugService *service : std::as_const(m_plugins))
579 service->engineAboutToBeRemoved(engine);
581 m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
583 for (QQmlDebugService *service : std::as_const(m_plugins))
584 service->engineRemoved(engine);
586 m_engineConditions.remove(engine);
591 QMutexLocker locker(&m_helloMutex);
592 QHash<QJSEngine *, EngineCondition>::ConstIterator i = m_engineConditions.constFind(engine);
594 return i != m_engineConditions.constEnd() && !i.value().isWaiting();
600 Q_ASSERT(!m_thread.isRunning());
602 if (!service || m_plugins.contains(name))
605 connect(service, &QQmlDebugService::messageToClient,
606 this, &QQmlDebugServerImpl::sendMessage);
607 connect(service, &QQmlDebugService::messagesToClient,
608 this, &QQmlDebugServerImpl::sendMessages);
610 connect(service, &QQmlDebugService::attachedToEngine,
611 this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
612 connect(service, &QQmlDebugService::detachedFromEngine,
613 this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
615 service->setState(QQmlDebugService::Unavailable);
616 m_plugins.insert(name, service);
624 Q_ASSERT(!m_thread.isRunning());
626 QQmlDebugService *service = m_plugins.value(name);
630 m_plugins.remove(name);
631 service->setState(QQmlDebugService::NotConnected);
633 disconnect(service, &QQmlDebugService::detachedFromEngine,
634 this, &QQmlDebugServerImpl::wakeEngine);
635 disconnect(service, &QQmlDebugService::attachedToEngine,
636 this, &QQmlDebugServerImpl::wakeEngine);
638 disconnect(service, &QQmlDebugService::messagesToClient,
639 this, &QQmlDebugServerImpl::sendMessages);
640 disconnect(service, &QQmlDebugService::messageToClient,
641 this, &QQmlDebugServerImpl::sendMessage);
649 Q_ASSERT(QThread::currentThread() == thread());
650 return m_connection && m_connection->isConnected() && m_protocol &&
651 m_clientPlugins.contains(name);
657 out << name << message;
658 m_protocol->send(out.data());
663 if (canSendMessage(name)) {
664 doSendMessage(name, message);
665 m_connection->flush();
669void QQmlDebugServerImpl::sendMessages(
const QString &name,
const QList<QByteArray> &messages)
671 if (canSendMessage(name)) {
674 for (
const QByteArray &message : messages)
676 m_protocol->send(out.data());
677 m_connection->flush();
684 Q_ASSERT(QThread::currentThread() == thread());
686 QMutexLocker locker(&m_helloMutex);
687 m_engineConditions[engine].wake();
692 Q_ASSERT_X(numServices == 0, Q_FUNC_INFO,
"Request to wait again before previous wait finished");
694 return numServices > 0 ? condition->wait(locked) :
true;
699 if (--numServices == 0)
700 condition->wakeAll();
701 Q_ASSERT_X(numServices >=0, Q_FUNC_INFO,
"Woken more often than #services.");
706 m_protocol =
new QPacketProtocol(socket,
this);
707 QObject::connect(m_protocol, &QPacketProtocol::readyRead,
708 this, &QQmlDebugServerImpl::receiveMessage);
709 QObject::connect(m_protocol, &QPacketProtocol::error,
710 this, &QQmlDebugServerImpl::protocolError);
713 m_protocol->waitForReadyRead(-1);
718 qWarning(
"QML Debugger: A protocol error has occurred! Giving up ...");
719 m_connection->disconnect();
721 m_protocol->deleteLater();
722 m_protocol =
nullptr;
725QQmlDebugConnector *QQmlDebugServerFactory::create(
const QString &key)
733#include "moc_qqmldebugserverfactory.cpp"
734#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
static void cleanupOnShutdown()