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>
17#include <QtCore/QAtomicInt>
19#include <QtCore/QPluginLoader>
20#include <QtCore/QStringList>
21#include <QtCore/QVector>
22#include <QtCore/qwaitcondition.h>
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
48Q_QML_DEBUG_PLUGIN_LOADER(QQmlDebugServerConnection)
50const int protocolVersion = 1;
51using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>;
64 void setPortRange(
int portFrom,
int portTo,
const QString &hostAddress)
66 m_pluginName = QLatin1String(
"QTcpServerConnection");
67 m_portFrom = portFrom;
69 m_hostAddress = hostAddress;
74 m_pluginName = QLatin1String(
"QLocalClientConnection");
75 m_fileName = fileName;
90 QString m_hostAddress;
108 bool addService(
const QString &name, QQmlDebugService *service)
override;
111 bool open(
const QVariantHash &configuration)
override;
120 friend class QQmlDebugServerFactory;
122 class EngineCondition {
124 EngineCondition() : numServices(0), condition(
new QWaitCondition) {}
126 bool waitForServices(QMutex *locked,
int numEngines);
127 bool isWaiting()
const {
return numServices > 0; }
134 QSharedPointer<QWaitCondition> condition;
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);
144 void receiveMessage();
145 void protocolError();
147 QQmlDebugServerConnection *m_connection;
148 QHash<QString, QQmlDebugService *> m_plugins;
149 QStringList m_clientPlugins;
153 QHash<QJSEngine *, EngineCondition> m_engineConditions;
155 mutable QMutex m_helloMutex;
156 QWaitCondition m_helloCondition;
158 QPacketProtocol *m_protocol;
159 QAtomicInt m_changeServiceStateCalls;
165 QQmlDebugConnector::instance());
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();
176 connect(&signalSource, &QObject::destroyed, server, [key, server](){
177 server->changeServiceState(key, QQmlDebugService::NotConnected);
178 }, Qt::QueuedConnection);
186 while (!server->m_changeServiceStateCalls.testAndSetOrdered(0, 0))
187 loop.processEvents();
190 server->m_thread.exit();
191 server->m_thread.wait();
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);
200 QMutexLocker connectionLocker(&m_server->m_helloMutex);
201 m_server->m_connection = connection;
202 connection->setServer(m_server);
203 m_server->m_helloCondition.wakeAll();
206 if (m_fileName.isEmpty()) {
207 if (!connection->setPortRange(m_portFrom, m_portTo, m_server->blockingMode(),
210 }
else if (!connection->setFileName(m_fileName, m_server->blockingMode())) {
215 connection->waitForConnection();
217 qWarning() <<
"QML Debugger: Couldn't load plugin" << m_pluginName;
224 QEventLoop eventLoop;
225 eventLoop.processEvents(QEventLoop::AllEvents);
230 return m_blockingMode;
242 m_connection(
nullptr),
244 m_blockingMode(
false)
246 static bool postRoutineAdded =
false;
247 if (!postRoutineAdded) {
249 postRoutineAdded =
true;
253 qRegisterMetaType<QList<QByteArray> >(
"QList<QByteArray>");
255 qRegisterMetaType<QQmlDebugService::State>(
"QQmlDebugService::State");
257 m_thread.setServer(
this);
258 moveToThread(&m_thread);
262 QObject::connect(&m_thread, &QThread::finished,
this, &QQmlDebugServerImpl::removeThread,
263 Qt::DirectConnection);
264 m_thread.setObjectName(QStringLiteral(
"QQmlDebugServerThread"));
270 if (m_thread.isRunning())
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());
286 if (m_thread.pluginName().isEmpty())
289 QMutexLocker locker(&m_helloMutex);
291 m_helloCondition.wait(&m_helloMutex);
292 if (m_blockingMode && !m_gotHello)
293 m_helloCondition.wait(&m_helloMutex);
300 const QString args = commandLineArguments();
311 QStringList services;
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);
319 const auto argsNext = argsIt + 1;
320 if (argsNext == argsItEnd)
323 portTo = argsNext->toString().toInt(&ok);
331 }
else if (strArgument.startsWith(QLatin1String(
"host:"))) {
332 hostAddress = strArgument.mid(5).toString();
333 }
else if (strArgument == QLatin1String(
"block")) {
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));
350 setServices(services);
351 m_blockingMode = block;
352 if (!fileName.isEmpty())
353 m_thread.setFileName(fileName);
355 m_thread.setPortRange(portFrom, portTo, hostAddress);
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- "
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- "
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- "
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'
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));
408 typedef QHash<QString, QQmlDebugService*>::const_iterator DebugServiceConstIt;
411 Q_ASSERT(QThread::currentThread() == thread());
416 QQmlDebugPacket in(m_protocol->read());
421 if (name == QLatin1String(
"QDeclarativeDebugServer")) {
426 in >> version >> m_clientPlugins;
430 in >> s_dataStreamVersion;
431 if (s_dataStreamVersion > QDataStream::Qt_DefaultCompiledVersion)
432 s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion;
435 bool clientSupportsMultiPackets =
false;
437 in >> clientSupportsMultiPackets;
443 QStringList pluginNames;
444 QList<
float> pluginVersions;
445 if (clientSupportsMultiPackets) {
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();
456 out << QString(QStringLiteral(
"QDeclarativeDebugClient")) << 0 << protocolVersion
457 << pluginNames << pluginVersions << dataStreamVersion();
459 m_protocol->send(out.data());
460 m_connection->flush();
462 QMutexLocker helloLock(&m_helloMutex);
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);
473 m_helloCondition.wakeAll();
475 }
else if (op == 1) {
477 QStringList oldClientPlugins = m_clientPlugins;
478 in >> m_clientPlugins;
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;
486 if (oldClientPlugins.contains(pluginName)
487 != m_clientPlugins.contains(pluginName)) {
488 m_changeServiceStateCalls.ref();
489 changeServiceState(iter.key(), newState);
494 qWarning(
"QML Debugger: Invalid control message %d.", op);
501 const auto iter = m_plugins.constFind(name);
502 if (iter == m_plugins.cend()) {
503 qWarning() <<
"QML Debugger: Message received for missing plugin" << name <<
'.';
505 QQmlDebugService *service = *iter;
507 while (!in.atEnd()) {
509 service->messageReceived(message);
513 qWarning(
"QML Debugger: Invalid hello message.");
520 QQmlDebugService::State newState)
523 Q_ASSERT(QThread::currentThread() == thread());
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);
532 m_changeServiceStateCalls.deref();
537 Q_ASSERT(m_thread.isFinished());
538 Q_ASSERT(QThread::currentThread() == thread());
540 QThread *parentThread = m_thread.thread();
543 m_connection =
nullptr;
546 moveToThread(parentThread);
551 return m_plugins.value(name);
557 Q_ASSERT(QThread::currentThread() != &m_thread);
559 QMutexLocker locker(&m_helloMutex);
560 Q_ASSERT(!m_engineConditions.contains(engine));
562 for (QQmlDebugService *service : std::as_const(m_plugins))
563 service->engineAboutToBeAdded(engine);
565 m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
567 for (QQmlDebugService *service : std::as_const(m_plugins))
568 service->engineAdded(engine);
574 Q_ASSERT(QThread::currentThread() != &m_thread);
576 QMutexLocker locker(&m_helloMutex);
577 Q_ASSERT(m_engineConditions.contains(engine));
579 for (QQmlDebugService *service : std::as_const(m_plugins))
580 service->engineAboutToBeRemoved(engine);
582 m_engineConditions[engine].waitForServices(&m_helloMutex, m_plugins.size());
584 for (QQmlDebugService *service : std::as_const(m_plugins))
585 service->engineRemoved(engine);
587 m_engineConditions.remove(engine);
592 QMutexLocker locker(&m_helloMutex);
593 QHash<QJSEngine *, EngineCondition>::ConstIterator i = m_engineConditions.constFind(engine);
595 return i != m_engineConditions.constEnd() && !i.value().isWaiting();
601 Q_ASSERT(!m_thread.isRunning());
603 if (!service || m_plugins.contains(name))
606 connect(service, &QQmlDebugService::messageToClient,
607 this, &QQmlDebugServerImpl::sendMessage);
608 connect(service, &QQmlDebugService::messagesToClient,
609 this, &QQmlDebugServerImpl::sendMessages);
611 connect(service, &QQmlDebugService::attachedToEngine,
612 this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
613 connect(service, &QQmlDebugService::detachedFromEngine,
614 this, &QQmlDebugServerImpl::wakeEngine, Qt::QueuedConnection);
616 service->setState(QQmlDebugService::Unavailable);
617 m_plugins.insert(name, service);
625 Q_ASSERT(!m_thread.isRunning());
627 QQmlDebugService *service = m_plugins.value(name);
631 m_plugins.remove(name);
632 service->setState(QQmlDebugService::NotConnected);
634 disconnect(service, &QQmlDebugService::detachedFromEngine,
635 this, &QQmlDebugServerImpl::wakeEngine);
636 disconnect(service, &QQmlDebugService::attachedToEngine,
637 this, &QQmlDebugServerImpl::wakeEngine);
639 disconnect(service, &QQmlDebugService::messagesToClient,
640 this, &QQmlDebugServerImpl::sendMessages);
641 disconnect(service, &QQmlDebugService::messageToClient,
642 this, &QQmlDebugServerImpl::sendMessage);
650 Q_ASSERT(QThread::currentThread() == thread());
651 return m_connection && m_connection->isConnected() && m_protocol &&
652 m_clientPlugins.contains(name);
658 out << name << message;
659 m_protocol->send(out.data());
664 if (canSendMessage(name)) {
665 doSendMessage(name, message);
666 m_connection->flush();
670void QQmlDebugServerImpl::sendMessages(
const QString &name,
const QList<QByteArray> &messages)
672 if (canSendMessage(name)) {
675 for (
const QByteArray &message : messages)
677 m_protocol->send(out.data());
678 m_connection->flush();
685 Q_ASSERT(QThread::currentThread() == thread());
687 QMutexLocker locker(&m_helloMutex);
688 m_engineConditions[engine].wake();
693 Q_ASSERT_X(numServices == 0, Q_FUNC_INFO,
"Request to wait again before previous wait finished");
695 return numServices > 0 ? condition->wait(locked) :
true;
700 if (--numServices == 0)
701 condition->wakeAll();
702 Q_ASSERT_X(numServices >=0, Q_FUNC_INFO,
"Woken more often than #services.");
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);
714 m_protocol->waitForReadyRead(-1);
719 qWarning(
"QML Debugger: A protocol error has occurred! Giving up ...");
720 m_connection->disconnect();
722 m_protocol->deleteLater();
723 m_protocol =
nullptr;
726QQmlDebugConnector *QQmlDebugServerFactory::create(
const QString &key)
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()