From e0edacb5598ffcb57b656fa6d622e18b9ad0d897 Mon Sep 17 00:00:00 2001 From: kevinye Date: Mon, 13 Apr 2026 09:47:43 +0800 Subject: [PATCH 1/3] Fix attach crash in complex Qt applications --- .history/core/probe_20260413092956.cpp | 999 ++++++++++++++ .history/core/probe_20260413094137.cpp | 1007 ++++++++++++++ .../quickinspector_20260413092956.cpp | 1190 ++++++++++++++++ .../quickinspector_20260413093758.cpp | 1201 +++++++++++++++++ .../quickinspector_20260413093955.cpp | 1201 +++++++++++++++++ .history/probe/hooks_20260413092956.cpp | 143 ++ .history/probe/hooks_20260413093443.cpp | 147 ++ core/probe.cpp | 10 +- plugins/quickinspector/quickinspector.cpp | 19 +- probe/hooks.cpp | 4 + 10 files changed, 5916 insertions(+), 5 deletions(-) create mode 100644 .history/core/probe_20260413092956.cpp create mode 100644 .history/core/probe_20260413094137.cpp create mode 100644 .history/plugins/quickinspector/quickinspector_20260413092956.cpp create mode 100644 .history/plugins/quickinspector/quickinspector_20260413093758.cpp create mode 100644 .history/plugins/quickinspector/quickinspector_20260413093955.cpp create mode 100644 .history/probe/hooks_20260413092956.cpp create mode 100644 .history/probe/hooks_20260413093443.cpp diff --git a/.history/core/probe_20260413092956.cpp b/.history/core/probe_20260413092956.cpp new file mode 100644 index 0000000000..bb162e56be --- /dev/null +++ b/.history/core/probe_20260413092956.cpp @@ -0,0 +1,999 @@ +/* + probe.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ +// krazy:excludeall=null,captruefalse,staticobjects + +#include + +#include "probe.h" +#include "enumrepositoryserver.h" +#include "execution.h" +#include "classesiconsrepositoryserver.h" +#include "metaobjectrepository.h" +#include "objectlistmodel.h" +#include "objecttreemodel.h" +#include "probesettings.h" +#include "probecontroller.h" +#include "problemcollector.h" +#include "toolmanager.h" +#include "toolpluginmodel.h" +#include "util.h" +#include "varianthandler.h" +#include "metaobjectregistry.h" +#include "favoriteobject.h" + +#include "remote/server.h" +#include "remote/remotemodelserver.h" +#include "remote/serverproxymodel.h" +#include "remote/selectionmodelserver.h" +#include "toolpluginerrormodel.h" +#include "probeguard.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IF_DEBUG(x) + +#ifdef ENABLE_EXPENSIVE_ASSERTS +#define EXPENSIVE_ASSERT(x) Q_ASSERT(x) +#else +#define EXPENSIVE_ASSERT(x) +#endif + +using namespace GammaRay; +using namespace std; + +QAtomicPointer Probe::s_instance = QAtomicPointer(nullptr); + +namespace GammaRay { +static void signal_begin_callback(QObject *caller, int method_index, void **argv) +{ + // Ignore event dispatcher signals + if (caller->inherits("QAbstractEventDispatcher")) + return; + + if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) + return; + + method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.signalBeginCallback) + callbacks.signalBeginCallback(caller, method_index, argv); + }); +} + +static void signal_end_callback(QObject *caller, int method_index) +{ + if (method_index == 0 || !Probe::instance()) + return; + + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()) + return; + if (!Probe::instance()->isValidObject(caller)) // implies filterObject() + return; // deleted in the slot + locker.unlock(); + + method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.signalEndCallback) + callbacks.signalEndCallback(caller, method_index); + }); +} + +static void slot_begin_callback(QObject *caller, int method_index, void **argv) +{ + if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) + return; + + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.slotBeginCallback) + callbacks.slotBeginCallback(caller, method_index, argv); + }); +} + +static void slot_end_callback(QObject *caller, int method_index) +{ + if (method_index == 0 || !Probe::instance()) + return; + + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(caller)) // implies filterObject() + return; // deleted in the slot + locker.unlock(); + + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.slotEndCallback) + callbacks.slotEndCallback(caller, method_index); + }); +} + +static QItemSelectionModel *selectionModelFactory(QAbstractItemModel *model) +{ + Q_ASSERT(!model->objectName().isEmpty()); + return new SelectionModelServer(model->objectName() + ".selection", model, Probe::instance()); +} +} + +// useful for debugging, dumps the object and all it's parents +// also usable from GDB! +void dumpObject(QObject *obj) +{ + if (!obj) { + cout << "QObject(0x0)" << endl; + return; + } + + const std::ios::fmtflags oldFlags(cout.flags()); + do { + cout << obj->metaObject()->className() << "(" << hex << obj << ")"; + obj = obj->parent(); + if (obj) + cout << " <- "; + } while (obj); + cout << endl; + cout.flags(oldFlags); +} + +struct Listener +{ + Listener() = default; + + bool trackDestroyed = true; + QVector addedBeforeProbeInstance; + + QHash constructionBacktracesForObjects; +}; + +Q_GLOBAL_STATIC(Listener, s_listener) + +// ensures proper information is returned by isValidObject by +// locking it in objectAdded/Removed +Q_GLOBAL_STATIC(QRecursiveMutex, s_lock) + +Probe::Probe(QObject *parent) + : QObject(parent) + , m_objectListModel(new ObjectListModel(this)) + , m_objectTreeModel(new ObjectTreeModel(this)) + , m_window(nullptr) + , m_metaObjectRegistry(new MetaObjectRegistry(this)) + , m_queueTimer(new QTimer(this)) + , m_server(nullptr) +{ + qputenv("DEBUGINFOD_URLS", QByteArray()); + + Q_ASSERT(thread() == qApp->thread()); + IF_DEBUG(cout << "attaching GammaRay probe" << endl;) + + StreamOperators::registerOperators(); + ProbeSettings::receiveSettings(); + + m_server = new Server(this); + + ObjectBroker::setSelectionModelFactoryCallback(selectionModelFactory); + ObjectBroker::registerObject(new ProbeController(this)); + m_toolManager = new ToolManager(this); + ObjectBroker::registerObject(m_toolManager); + ObjectBroker::registerObject(new FavoriteObject(this)); + + m_problemCollector = new ProblemCollector(this); + + ObjectBroker::registerObject(EnumRepositoryServer::create(this)); + ClassesIconsRepositoryServer::create(this); + registerModel(QStringLiteral("com.kdab.GammaRay.ObjectTree"), m_objectTreeModel); + registerModel(QStringLiteral("com.kdab.GammaRay.ObjectList"), m_objectListModel); + + ToolPluginModel *toolPluginModel = new ToolPluginModel( + m_toolManager->toolPluginManager()->plugins(), this); + registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginModel"), toolPluginModel); + ToolPluginErrorModel *toolPluginErrorModel = new ToolPluginErrorModel(m_toolManager->toolPluginManager()->errors(), this); + registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginErrorModel"), toolPluginErrorModel); + + m_queueTimer->setSingleShot(true); + m_queueTimer->setInterval(0); + connect(m_queueTimer, &QTimer::timeout, + this, &Probe::processQueuedObjectChanges); + + m_previousSignalSpyCallbackSet = qt_signal_spy_callback_set.loadRelaxed(); + + connect(this, &Probe::objectCreated, m_metaObjectRegistry, &MetaObjectRegistry::objectAdded); + connect(this, &Probe::objectDestroyed, m_metaObjectRegistry, &MetaObjectRegistry::objectRemoved); +} + +Probe::~Probe() +{ + emit aboutToDetach(); + IF_DEBUG(cerr << "detaching GammaRay probe" << endl;) + + // Remove hooks + qtHookData[QHooks::AddQObject] = 0; + qtHookData[QHooks::RemoveQObject] = 0; + qtHookData[QHooks::Startup] = 0; + + qt_register_signal_spy_callbacks(m_previousSignalSpyCallbackSet); + + ObjectBroker::clear(); + ProbeSettings::resetLauncherIdentifier(); + MetaObjectRepository::instance()->clear(); + VariantHandler::clear(); + + s_instance = QAtomicPointer(nullptr); +} + +void Probe::setWindow(QObject *window) +{ + m_window = window; +} + +QObject *Probe::window() const +{ + return m_window; +} + +MetaObjectRegistry *Probe::metaObjectRegistry() const +{ + return m_metaObjectRegistry; +} + +Probe *GammaRay::Probe::instance() +{ + return s_instance.loadRelaxed(); +} + +bool Probe::isInitialized() +{ + return instance(); +} + +bool Probe::canShowWidgets() +{ + return QCoreApplication::instance()->inherits("QApplication"); +} + +void Probe::createProbe(bool findExisting) +{ + Q_ASSERT(qApp); + Q_ASSERT(!isInitialized()); + + // first create the probe and its children + // we must not hold the object lock here as otherwise we can deadlock + // with other QObject's we create and other threads are using. One + // example are QAbstractSocketEngine. + IF_DEBUG(cout << "setting up new probe instance" << endl;) + Probe *probe = nullptr; + { + ProbeGuard guard; + probe = new Probe; + } + IF_DEBUG(cout << "done setting up new probe instance" << endl;) + + connect(qApp, &QCoreApplication::aboutToQuit, probe, &Probe::shutdown); + + // Our safety net, if there's no call to QCoreApplication::exec() we'll never receive the aboutToQuit() signal + // Make sure we still cleanup safely after the application instance got destroyed + connect(qApp, &QObject::destroyed, probe, &Probe::shutdown); + + // now we can get the lock and add items which where added before this point in time + { + QMutexLocker lock(s_lock()); + // now we set the instance while holding the lock, + // all future calls to object{Added,Removed} will + // act directly on the data structures there instead + // of using addedBeforeProbeInstance + // this will only happen _after_ the object lock above is released though + Q_ASSERT(!instance()); + + s_instance = QAtomicPointer(probe); + + // add objects to the probe that were tracked before its creation + foreach (QObject *obj, s_listener()->addedBeforeProbeInstance) { + objectAdded(obj); + } + s_listener()->addedBeforeProbeInstance.clear(); + + // try to find existing objects by other means + if (findExisting) + probe->findExistingObjects(); + } + + // eventually initialize the rest + QMetaObject::invokeMethod(probe, "delayedInit", Qt::QueuedConnection); +} + +void Probe::resendServerAddress() +{ + Q_ASSERT(isInitialized()); + Q_ASSERT(m_server); + if (!m_server->isListening()) // already connected + return; + ProbeSettings::receiveSettings(); + ProbeSettings::sendServerAddress(m_server->externalAddress()); +} + +void Probe::startupHookReceived() +{ +#ifdef Q_OS_ANDROID + QDir root = QDir::home(); + root.cdUp(); + Paths::setRootPath(root.absolutePath()); +#endif + s_listener()->trackDestroyed = false; +} + +void Probe::delayedInit() +{ + QCoreApplication::instance()->installEventFilter(this); + + QString appName = qApp->applicationName(); + if (appName.isEmpty() && !qApp->arguments().isEmpty()) { + appName = qApp->arguments().first().remove(qApp->applicationDirPath()); + if (appName.startsWith('.')) + appName = appName.right(appName.length() - 1); + if (appName.startsWith('/')) + appName = appName.right(appName.length() - 1); + } + if (appName.isEmpty()) + appName = tr("PID %1").arg(qApp->applicationPid()); + m_server->setLabel(appName); + // The applicationName might be translated, so let's go with the application file base name + m_server->setKey(QFileInfo(qApp->applicationFilePath()).completeBaseName()); + m_server->setPid(qApp->applicationPid()); + + if (ProbeSettings::value(QStringLiteral("RemoteAccessEnabled"), true).toBool()) { + const auto serverStarted = m_server->listen(); + if (serverStarted) { + ProbeSettings::sendServerAddress(m_server->externalAddress()); + } else { + ProbeSettings::sendServerLaunchError(m_server->errorString()); + } + } + + if (ProbeSettings::value(QStringLiteral("InProcessUi"), false).toBool()) + showInProcessUi(); +} + +void Probe::shutdown() +{ + delete this; +} + +void Probe::showInProcessUi() +{ + if (!canShowWidgets()) { + cerr << "Unable to show in-process UI in a non-QWidget based application." << endl; + return; + } + + IF_DEBUG(cout << "creating GammaRay::MainWindow" << endl;) + ProbeGuard guard; + + QLibrary lib; + for (auto &path : Paths::pluginPaths(QStringLiteral(GAMMARAY_PROBE_ABI))) { + path += QStringLiteral("/gammaray_inprocessui"); +#if defined(GAMMARAY_INSTALL_QT_LAYOUT) + path += '-'; + path += GAMMARAY_PROBE_ABI; +#else +#if !defined(Q_OS_MAC) +#if defined(QT_DEBUG) + path += QLatin1String(GAMMARAY_DEBUG_POSTFIX); +#endif +#endif +#endif + lib.setFileName(path); + if (lib.load()) + break; + } + + if (!lib.isLoaded()) { + std::cerr << "Failed to load in-process UI module: " + << qPrintable(lib.errorString()) + << std::endl; + } else { + void (*factory)() = reinterpret_cast(lib.resolve("gammaray_create_inprocess_mainwindow")); + if (!factory) + std::cerr << Q_FUNC_INFO << ' ' << qPrintable(lib.errorString()) << endl; + else + factory(); + } + + IF_DEBUG(cout << "creation done" << endl;) +} + +bool Probe::filterObject(QObject *obj) const +{ + QSet visitedObjects; + int iteration = 0; + QObject *o = obj; + do { + if (iteration > 100) { + // Probably we have a loop in the tree. Do loop detection. + if (visitedObjects.contains(o)) { + std::cerr << "We detected a loop in the object tree for object " << o; + if (!o->objectName().isEmpty()) + std::cerr << " \"" << qPrintable(o->objectName()) << "\""; + std::cerr << " (" << o->metaObject()->className() << ")." << std::endl; + return true; + } + visitedObjects << o; + } + ++iteration; + + if (o == this || o == window() || (qstrncmp(o->metaObject()->className(), "GammaRay::", 10) == 0)) { + return true; + } + o = o->parent(); + } while (o); + return false; +} + +void Probe::registerModel(const QString &objectName, QAbstractItemModel *model) +{ + auto *ms = new RemoteModelServer(objectName, model); + ms->setModel(model); + ObjectBroker::registerModelInternal(objectName, model); +} + +QAbstractItemModel *Probe::objectListModel() const +{ + return m_objectListModel; +} + +const QVector &Probe::allQObjects() const +{ + return m_objectListModel->objects(); +} + +QAbstractItemModel *Probe::objectTreeModel() const +{ + return m_objectTreeModel; +} + +ProblemCollector *Probe::problemCollector() const +{ + return m_problemCollector; +} + +QRecursiveMutex *Probe::objectLock() +{ + return s_lock(); +} + +/* + * We need to handle 4 different cases in here: + * (1) our thread, from ctor: + * - wait until next event-loop re-entry of our thread + * - emit objectCreated if object still valid + * (2) our thread, after ctor: + * - emit objectCreated right away + * (3) other thread, from ctor: + * - wait until next event-loop re-entry in other thread (FIXME: we do not currently do this!!) + * - post information to our thread + * (4) other thread, after ctor: + * - post information to our thread + * - emit objectCreated there right away if object still valid + * + * Pre-conditions: lock may or may not be held already, arbitrary thread + */ +void Probe::objectAdded(QObject *obj, bool fromCtor) +{ + if (obj == nullptr) + return; + QMutexLocker lock(s_lock()); + + // attempt to ignore objects created by GammaRay itself, especially short-lived ones + if (fromCtor && ProbeGuard::insideProbe() && obj->thread() == QThread::currentThread()) + return; + + // ignore objects created when global statics are already getting destroyed (on exit) + if (s_listener.isDestroyed()) + return; + + + if (Execution::hasFastStackTrace() && fromCtor) { + s_listener()->constructionBacktracesForObjects.insert(obj, Execution::stackTrace(32, 2)); // skip 2: this and the hook function calling us + } + + if (!isInitialized()) { + IF_DEBUG(cout + << "objectAdded Before: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + s_listener()->addedBeforeProbeInstance << obj; + return; + } + + if (instance()->filterObject(obj)) { + IF_DEBUG(cout + << "objectAdded Filter: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + return; + } + + if (instance()->m_validObjects.contains(obj)) { + // this happens when we get a child event before the objectAdded call from the ctor + // or when we add an item from addedBeforeProbeInstance who got added already + // due to the add-parent-before-child logic + IF_DEBUG(cout + << "objectAdded Known: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + return; + } + + // make sure we already know the parent + if (obj->parent() && !instance()->m_validObjects.contains(obj->parent())) + objectAdded(obj->parent(), fromCtor); + Q_ASSERT(!obj->parent() || instance()->m_validObjects.contains(obj->parent())); + + instance()->m_validObjects << obj; + + if (!fromCtor && obj->parent() && instance()->isObjectCreationQueued(obj->parent())) { + // when a child event triggers a call to objectAdded while inside the ctor + // the parent is already tracked but it's call to objectFullyConstructed + // was delayed. hence we must do the same for the child for integrity + fromCtor = true; + } + + IF_DEBUG(cout << "objectAdded: " << hex << obj + << (fromCtor ? " (from ctor)" : "") + << ", p: " << obj->parent() << endl;) + + if (fromCtor) + instance()->queueCreatedObject(obj); + else + instance()->objectFullyConstructed(obj); +} + +// pre-conditions: lock may or may not be held already, our thread +void Probe::processQueuedObjectChanges() +{ + QMutexLocker lock(s_lock()); + + IF_DEBUG(cout << Q_FUNC_INFO << " " << m_queuedObjectChanges.size() << endl;) + + // must be called from the main thread via timeout + Q_ASSERT(QThread::currentThread() == thread()); + + const auto queuedObjectChanges = m_queuedObjectChanges; // copy, in case this gets modified while we iterate (which can actually happen) + for (const auto &change : queuedObjectChanges) { + switch (change.type) { + case ObjectChange::Create: + objectFullyConstructed(change.obj); + break; + case ObjectChange::Destroy: + emit objectDestroyed(change.obj); + break; + } + } + + IF_DEBUG(cout << Q_FUNC_INFO << " done" << endl;) + + m_queuedObjectChanges.clear(); + + for (QObject *obj : std::as_const(m_pendingReparents)) { + if (!isValidObject(obj)) + continue; + if (filterObject(obj)) // the move might have put it under a hidden parent + objectRemoved(obj); + else + emit objectReparented(obj); + } + m_pendingReparents.clear(); +} + +// pre-condition: lock is held already, our thread +void Probe::objectFullyConstructed(QObject *obj) +{ + Q_ASSERT(thread() == QThread::currentThread()); + + if (!m_validObjects.contains(obj)) { + // deleted already + IF_DEBUG(cout << "stale fully constructed: " << hex << obj << endl;) + return; + } + + if (filterObject(obj)) { + // when the call was delayed from the ctor construction, + // the parent might not have been set properly yet. hence + // apply the filter again + m_validObjects.remove(obj); + IF_DEBUG(cout << "now filtered fully constructed: " << hex << obj << endl;) + return; + } + + IF_DEBUG(cout << "fully constructed: " << hex << obj << endl;) + + // ensure we know all our ancestors already + for (QObject *parent = obj->parent(); parent; parent = parent->parent()) { + if (!m_validObjects.contains(parent)) { + objectAdded(parent); // will also handle any further ancestors + break; + } + } + Q_ASSERT(!obj->parent() || m_validObjects.contains(obj->parent())); + + m_toolManager->objectAdded(obj); + emit objectCreated(obj); +} + +/* + * We have two cases to consider here: + * (1) our thread: + * - emit objectDestroyed() right away + * (2) other thread: + * - post information to our thread, emit objectDestroyed() there + * + * pre-conditions: arbitrary thread, lock may or may not be held already + */ +void Probe::objectRemoved(QObject *obj) +{ + QMutexLocker lock(s_lock()); + + if (!isInitialized()) { + IF_DEBUG(cout + << "objectRemoved Before: " + << hex << obj + << " have statics: " << s_listener() << endl;) + + if (!s_listener()) + return; + + QVector &addedBefore = s_listener()->addedBeforeProbeInstance; + for (auto it = addedBefore.begin(); it != addedBefore.end();) { + if (*it == obj) + it = addedBefore.erase(it); + else + ++it; + } + return; + } + + IF_DEBUG(cout << "object removed:" << hex << obj << " " << obj->parent() << endl;) + + bool success = instance()->m_validObjects.remove(obj); + if (!success) { + // object was not tracked by the probe, probably a gammaray object + EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); + return; + } + + instance()->purgeChangesForObject(obj); + EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); + + if (instance()->thread() == QThread::currentThread()) + emit instance()->objectDestroyed(obj); + else + instance()->queueDestroyedObject(obj); +} + +void Probe::handleObjectDestroyed(QObject *obj) +{ + objectRemoved(obj); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::queueCreatedObject(QObject *obj) +{ + EXPENSIVE_ASSERT(!isObjectCreationQueued(obj)); + + ObjectChange c; + c.obj = obj; + c.type = ObjectChange::Create; + m_queuedObjectChanges.push_back(c); + notifyQueuedObjectChanges(); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::queueDestroyedObject(QObject *obj) +{ + ObjectChange c; + c.obj = obj; + c.type = ObjectChange::Destroy; + m_queuedObjectChanges.push_back(c); + notifyQueuedObjectChanges(); +} + +// pre-condition: we have the lock, arbitrary thread +bool Probe::isObjectCreationQueued(QObject *obj) const +{ + return std::find_if(m_queuedObjectChanges.begin(), m_queuedObjectChanges.end(), + [obj](const ObjectChange &c) { + return c.obj == obj && c.type == Probe::ObjectChange::Create; + }) + != m_queuedObjectChanges.end(); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::purgeChangesForObject(QObject *obj) +{ + for (int i = 0; i < m_queuedObjectChanges.size(); ++i) { + if (m_queuedObjectChanges.at(i).obj == obj + && m_queuedObjectChanges.at(i).type == ObjectChange::Create) { + m_queuedObjectChanges.remove(i); + return; + } + } +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::notifyQueuedObjectChanges() +{ + if (m_queueTimer->isActive()) + return; + + if (thread() == QThread::currentThread()) { + m_queueTimer->start(); + } else { + static QMetaMethod m; + if (m.methodIndex() < 0) { + const auto idx = QTimer::staticMetaObject.indexOfMethod("start()"); + Q_ASSERT(idx >= 0); + m = QTimer::staticMetaObject.method(idx); + Q_ASSERT(m.methodIndex() >= 0); + } + m.invoke(m_queueTimer, Qt::QueuedConnection); + } +} + +bool Probe::eventFilter(QObject *receiver, QEvent *event) +{ + if (ProbeGuard::insideProbe() && receiver->thread() == QThread::currentThread()) + return QObject::eventFilter(receiver, event); + + if (event->type() == QEvent::ChildAdded || event->type() == QEvent::ChildRemoved) { + QChildEvent *childEvent = static_cast(event); + QObject *obj = childEvent->child(); + + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(obj); + const bool filtered = filterObject(obj); + + IF_DEBUG(cout << "child event: " << hex << obj << ", p: " << obj->parent() << dec + << ", tracked: " << tracked + << ", filtered: " << filtered + << ", type: " << (childEvent->added() ? "added" : "removed") << endl;) + + if (!filtered && childEvent->added()) { + if (!tracked) { + // was not tracked before, add to all models + // child added events are sent before qt_addObject is called, + // so we assumes this comes from the ctor + objectAdded(obj, true); + } else if (!isObjectCreationQueued(obj) && !isObjectCreationQueued(obj->parent()) && isValidObject(obj->parent())) { + // object is known already, just update the position in the tree + // BUT: only when we did not queue this item before + IF_DEBUG(cout << "update pos: " << hex << obj << endl;) + m_pendingReparents.removeAll(obj); + emit objectReparented(obj); + } else if (!isValidObject(obj->parent())) { + objectAdded(obj->parent()); + m_pendingReparents.push_back(obj); + notifyQueuedObjectChanges(); + } + } else if (tracked) { + // defer processing this until we know its final location + m_pendingReparents.push_back(obj); + notifyQueuedObjectChanges(); + } + } + + // widget only unfortunately, but more precise than ChildAdded/Removed... + if (event->type() == QEvent::ParentChange) { + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(receiver); + const bool filtered = filterObject(receiver); + const bool parentTracked = m_validObjects.contains(receiver->parent()); + + if (!filtered && tracked && !isObjectCreationQueued(receiver) + && !isObjectCreationQueued(receiver->parent()) && parentTracked) { + m_pendingReparents.removeAll(receiver); + emit objectReparented(receiver); + } else if (!parentTracked) { + objectAdded(receiver->parent()); + m_pendingReparents.push_back(receiver); + notifyQueuedObjectChanges(); + } + } + + // we have no preloading hooks, so recover all objects we see + if (needsObjectDiscovery() && event->type() != QEvent::ChildAdded + && event->type() != QEvent::ChildRemoved + && event->type() != QEvent::ParentChange // already handled above + && event->type() != QEvent::Destroy + && event->type() != QEvent::WinIdChange // unsafe since emitted from dtors + && !filterObject(receiver)) { + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(receiver); + if (!tracked) + discoverObject(receiver); + } + + // filters provided by plugins + if (!filterObject(receiver)) { + for (QObject *filter : std::as_const(m_globalEventFilters)) { + filter->eventFilter(receiver, event); + } + } + + return QObject::eventFilter(receiver, event); +} + +void Probe::findExistingObjects() +{ + discoverObject(QCoreApplication::instance()); + + if (auto guiApp = qobject_cast(QCoreApplication::instance())) { + foreach (auto window, guiApp->allWindows()) { + discoverObject(window); + } + } +} + +void Probe::discoverObject(QObject *object) +{ + if (!object) + return; + + QMutexLocker lock(s_lock()); + if (m_validObjects.contains(object)) + return; + + objectAdded(object); + foreach (QObject *child, object->children()) { + discoverObject(child); + } +} + +void Probe::installGlobalEventFilter(QObject *filter) +{ + Q_ASSERT(!m_globalEventFilters.contains(filter)); + m_globalEventFilters.push_back(filter); +} + +bool Probe::needsObjectDiscovery() +{ + return s_listener()->trackDestroyed; +} + +bool Probe::hasReliableObjectTracking() +{ + return true; // qHooks available, which works independent of the injector used +} + +void Probe::selectObject(QObject *object, const QPoint &pos) +{ + const auto tools = m_toolManager->toolsForObject(object); + m_toolManager->selectTool(tools.value(0)); + emit objectSelected(object, pos); +} + +void Probe::selectObject(QObject *object, const QString &toolId, const QPoint &pos) +{ + if (!m_toolManager->hasTool(toolId)) { + std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; + return; + } + + m_toolManager->selectTool(toolId); + emit objectSelected(object, pos); +} + +void Probe::selectObject(void *object, const QString &typeName) +{ + const auto tools = m_toolManager->toolsForObject(object, typeName); + const QString toolId = tools.value(0); + + if (!m_toolManager->hasTool(toolId)) { + std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; + return; + } + + m_toolManager->selectTool(tools.value(0)); + emit nonQObjectSelected(object, typeName); +} + +void Probe::markObjectAsFavorite(QObject *object) +{ + { + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(object)) + return; // deleted in the slot + } + + Q_EMIT objectFavorited(object); +} + +void Probe::removeObjectAsFavorite(QObject *object) +{ + { + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(object)) + return; // deleted in the slot + } + Q_EMIT objectUnfavorited(object); +} + +void Probe::registerSignalSpyCallbackSet(const SignalSpyCallbackSet &callbacks) +{ + if (callbacks.isNull()) + return; + m_signalSpyCallbacks.push_back(callbacks); + setupSignalSpyCallbacks(); +} + +void Probe::setupSignalSpyCallbacks() +{ + // memory management is with us for Qt >= 5.14, therefore static here! + static QSignalSpyCallbackSet cbs = { nullptr, nullptr, nullptr, nullptr }; + foreach (const auto &it, m_signalSpyCallbacks) { + if (it.signalBeginCallback) + cbs.signal_begin_callback = signal_begin_callback; + if (it.signalEndCallback) + cbs.signal_end_callback = signal_end_callback; + if (it.slotBeginCallback) + cbs.slot_begin_callback = slot_begin_callback; + if (it.slotEndCallback) + cbs.slot_end_callback = slot_end_callback; + } + qt_register_signal_spy_callbacks(&cbs); +} + +template +void Probe::executeSignalCallback(const Func &func) +{ + std::for_each(instance()->m_signalSpyCallbacks.constBegin(), + instance()->m_signalSpyCallbacks.constEnd(), + func); +} + +SourceLocation Probe::objectCreationSourceLocation(const QObject *object) +{ + QObject *key = const_cast(object); + if (!s_listener()->constructionBacktracesForObjects.contains(key)) { + IF_DEBUG(std::cout << "No backtrace for object available" << object << "." << std::endl;) + return SourceLocation(); + } + + const auto &st = s_listener()->constructionBacktracesForObjects.value(key); + int distanceToQObject = 0; + + const QMetaObject *metaObject = object->metaObject(); + while (metaObject && metaObject != &QObject::staticMetaObject) { + distanceToQObject++; + metaObject = metaObject->superClass(); + } + + const auto frame = Execution::resolveOne(st, distanceToQObject + 1); + return frame.location; +} + +Execution::Trace Probe::objectCreationStackTrace(QObject *object) +{ + return s_listener()->constructionBacktracesForObjects.value(object); +} diff --git a/.history/core/probe_20260413094137.cpp b/.history/core/probe_20260413094137.cpp new file mode 100644 index 0000000000..85bdda8160 --- /dev/null +++ b/.history/core/probe_20260413094137.cpp @@ -0,0 +1,1007 @@ +/* + probe.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ +// krazy:excludeall=null,captruefalse,staticobjects + +#include + +#include "probe.h" +#include "enumrepositoryserver.h" +#include "execution.h" +#include "classesiconsrepositoryserver.h" +#include "metaobjectrepository.h" +#include "objectlistmodel.h" +#include "objecttreemodel.h" +#include "probesettings.h" +#include "probecontroller.h" +#include "problemcollector.h" +#include "toolmanager.h" +#include "toolpluginmodel.h" +#include "util.h" +#include "varianthandler.h" +#include "metaobjectregistry.h" +#include "favoriteobject.h" + +#include "remote/server.h" +#include "remote/remotemodelserver.h" +#include "remote/serverproxymodel.h" +#include "remote/selectionmodelserver.h" +#include "toolpluginerrormodel.h" +#include "probeguard.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IF_DEBUG(x) + +#ifdef ENABLE_EXPENSIVE_ASSERTS +#define EXPENSIVE_ASSERT(x) Q_ASSERT(x) +#else +#define EXPENSIVE_ASSERT(x) +#endif + +using namespace GammaRay; +using namespace std; + +QAtomicPointer Probe::s_instance = QAtomicPointer(nullptr); + +namespace GammaRay { +static void signal_begin_callback(QObject *caller, int method_index, void **argv) +{ + // Ignore event dispatcher signals + if (caller->inherits("QAbstractEventDispatcher")) + return; + + if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) + return; + + method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.signalBeginCallback) + callbacks.signalBeginCallback(caller, method_index, argv); + }); +} + +static void signal_end_callback(QObject *caller, int method_index) +{ + if (method_index == 0 || !Probe::instance()) + return; + + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()) + return; + if (!Probe::instance()->isValidObject(caller)) // implies filterObject() + return; // deleted in the slot + locker.unlock(); + + method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.signalEndCallback) + callbacks.signalEndCallback(caller, method_index); + }); +} + +static void slot_begin_callback(QObject *caller, int method_index, void **argv) +{ + if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) + return; + + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.slotBeginCallback) + callbacks.slotBeginCallback(caller, method_index, argv); + }); +} + +static void slot_end_callback(QObject *caller, int method_index) +{ + if (method_index == 0 || !Probe::instance()) + return; + + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(caller)) // implies filterObject() + return; // deleted in the slot + locker.unlock(); + + Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { + if (callbacks.slotEndCallback) + callbacks.slotEndCallback(caller, method_index); + }); +} + +static QItemSelectionModel *selectionModelFactory(QAbstractItemModel *model) +{ + Q_ASSERT(!model->objectName().isEmpty()); + return new SelectionModelServer(model->objectName() + ".selection", model, Probe::instance()); +} +} + +// useful for debugging, dumps the object and all it's parents +// also usable from GDB! +void dumpObject(QObject *obj) +{ + if (!obj) { + cout << "QObject(0x0)" << endl; + return; + } + + const std::ios::fmtflags oldFlags(cout.flags()); + do { + cout << obj->metaObject()->className() << "(" << hex << obj << ")"; + obj = obj->parent(); + if (obj) + cout << " <- "; + } while (obj); + cout << endl; + cout.flags(oldFlags); +} + +struct Listener +{ + Listener() = default; + + bool trackDestroyed = true; + QVector addedBeforeProbeInstance; + + QHash constructionBacktracesForObjects; +}; + +Q_GLOBAL_STATIC(Listener, s_listener) + +// ensures proper information is returned by isValidObject by +// locking it in objectAdded/Removed +Q_GLOBAL_STATIC(QRecursiveMutex, s_lock) + +Probe::Probe(QObject *parent) + : QObject(parent) + , m_objectListModel(new ObjectListModel(this)) + , m_objectTreeModel(new ObjectTreeModel(this)) + , m_window(nullptr) + , m_metaObjectRegistry(new MetaObjectRegistry(this)) + , m_queueTimer(new QTimer(this)) + , m_server(nullptr) +{ + qputenv("DEBUGINFOD_URLS", QByteArray()); + + Q_ASSERT(thread() == qApp->thread()); + IF_DEBUG(cout << "attaching GammaRay probe" << endl;) + + StreamOperators::registerOperators(); + ProbeSettings::receiveSettings(); + + m_server = new Server(this); + + ObjectBroker::setSelectionModelFactoryCallback(selectionModelFactory); + ObjectBroker::registerObject(new ProbeController(this)); + m_toolManager = new ToolManager(this); + ObjectBroker::registerObject(m_toolManager); + ObjectBroker::registerObject(new FavoriteObject(this)); + + m_problemCollector = new ProblemCollector(this); + + ObjectBroker::registerObject(EnumRepositoryServer::create(this)); + ClassesIconsRepositoryServer::create(this); + registerModel(QStringLiteral("com.kdab.GammaRay.ObjectTree"), m_objectTreeModel); + registerModel(QStringLiteral("com.kdab.GammaRay.ObjectList"), m_objectListModel); + + ToolPluginModel *toolPluginModel = new ToolPluginModel( + m_toolManager->toolPluginManager()->plugins(), this); + registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginModel"), toolPluginModel); + ToolPluginErrorModel *toolPluginErrorModel = new ToolPluginErrorModel(m_toolManager->toolPluginManager()->errors(), this); + registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginErrorModel"), toolPluginErrorModel); + + m_queueTimer->setSingleShot(true); + m_queueTimer->setInterval(0); + connect(m_queueTimer, &QTimer::timeout, + this, &Probe::processQueuedObjectChanges); + + m_previousSignalSpyCallbackSet = qt_signal_spy_callback_set.loadRelaxed(); + + connect(this, &Probe::objectCreated, m_metaObjectRegistry, &MetaObjectRegistry::objectAdded); + connect(this, &Probe::objectDestroyed, m_metaObjectRegistry, &MetaObjectRegistry::objectRemoved); +} + +Probe::~Probe() +{ + emit aboutToDetach(); + IF_DEBUG(cerr << "detaching GammaRay probe" << endl;) + + // Remove hooks + qtHookData[QHooks::AddQObject] = 0; + qtHookData[QHooks::RemoveQObject] = 0; + qtHookData[QHooks::Startup] = 0; + + qt_register_signal_spy_callbacks(m_previousSignalSpyCallbackSet); + + ObjectBroker::clear(); + ProbeSettings::resetLauncherIdentifier(); + MetaObjectRepository::instance()->clear(); + VariantHandler::clear(); + + s_instance = QAtomicPointer(nullptr); +} + +void Probe::setWindow(QObject *window) +{ + m_window = window; +} + +QObject *Probe::window() const +{ + return m_window; +} + +MetaObjectRegistry *Probe::metaObjectRegistry() const +{ + return m_metaObjectRegistry; +} + +Probe *GammaRay::Probe::instance() +{ + return s_instance.loadRelaxed(); +} + +bool Probe::isInitialized() +{ + return instance(); +} + +bool Probe::canShowWidgets() +{ + return QCoreApplication::instance()->inherits("QApplication"); +} + +void Probe::createProbe(bool findExisting) +{ + Q_ASSERT(qApp); + Q_ASSERT(!isInitialized()); + + // first create the probe and its children + // we must not hold the object lock here as otherwise we can deadlock + // with other QObject's we create and other threads are using. One + // example are QAbstractSocketEngine. + IF_DEBUG(cout << "setting up new probe instance" << endl;) + Probe *probe = nullptr; + { + ProbeGuard guard; + probe = new Probe; + } + IF_DEBUG(cout << "done setting up new probe instance" << endl;) + + connect(qApp, &QCoreApplication::aboutToQuit, probe, &Probe::shutdown); + + // Our safety net, if there's no call to QCoreApplication::exec() we'll never receive the aboutToQuit() signal + // Make sure we still cleanup safely after the application instance got destroyed + connect(qApp, &QObject::destroyed, probe, &Probe::shutdown); + + // now we can get the lock and add items which where added before this point in time + { + QMutexLocker lock(s_lock()); + // now we set the instance while holding the lock, + // all future calls to object{Added,Removed} will + // act directly on the data structures there instead + // of using addedBeforeProbeInstance + // this will only happen _after_ the object lock above is released though + Q_ASSERT(!instance()); + + s_instance = QAtomicPointer(probe); + + // add objects to the probe that were tracked before its creation + foreach (QObject *obj, s_listener()->addedBeforeProbeInstance) { + objectAdded(obj); + } + s_listener()->addedBeforeProbeInstance.clear(); + + // try to find existing objects by other means + if (findExisting) + probe->findExistingObjects(); + } + + // eventually initialize the rest + QMetaObject::invokeMethod(probe, "delayedInit", Qt::QueuedConnection); +} + +void Probe::resendServerAddress() +{ + Q_ASSERT(isInitialized()); + Q_ASSERT(m_server); + if (!m_server->isListening()) // already connected + return; + ProbeSettings::receiveSettings(); + ProbeSettings::sendServerAddress(m_server->externalAddress()); +} + +void Probe::startupHookReceived() +{ +#ifdef Q_OS_ANDROID + QDir root = QDir::home(); + root.cdUp(); + Paths::setRootPath(root.absolutePath()); +#endif + s_listener()->trackDestroyed = false; +} + +void Probe::delayedInit() +{ + QCoreApplication::instance()->installEventFilter(this); + + QString appName = qApp->applicationName(); + if (appName.isEmpty() && !qApp->arguments().isEmpty()) { + appName = qApp->arguments().first().remove(qApp->applicationDirPath()); + if (appName.startsWith('.')) + appName = appName.right(appName.length() - 1); + if (appName.startsWith('/')) + appName = appName.right(appName.length() - 1); + } + if (appName.isEmpty()) + appName = tr("PID %1").arg(qApp->applicationPid()); + m_server->setLabel(appName); + // The applicationName might be translated, so let's go with the application file base name + m_server->setKey(QFileInfo(qApp->applicationFilePath()).completeBaseName()); + m_server->setPid(qApp->applicationPid()); + + if (ProbeSettings::value(QStringLiteral("RemoteAccessEnabled"), true).toBool()) { + const auto serverStarted = m_server->listen(); + if (serverStarted) { + ProbeSettings::sendServerAddress(m_server->externalAddress()); + } else { + ProbeSettings::sendServerLaunchError(m_server->errorString()); + } + } + + if (ProbeSettings::value(QStringLiteral("InProcessUi"), false).toBool()) + showInProcessUi(); +} + +void Probe::shutdown() +{ + delete this; +} + +void Probe::showInProcessUi() +{ + if (!canShowWidgets()) { + cerr << "Unable to show in-process UI in a non-QWidget based application." << endl; + return; + } + + IF_DEBUG(cout << "creating GammaRay::MainWindow" << endl;) + ProbeGuard guard; + + QLibrary lib; + for (auto &path : Paths::pluginPaths(QStringLiteral(GAMMARAY_PROBE_ABI))) { + path += QStringLiteral("/gammaray_inprocessui"); +#if defined(GAMMARAY_INSTALL_QT_LAYOUT) + path += '-'; + path += GAMMARAY_PROBE_ABI; +#else +#if !defined(Q_OS_MAC) +#if defined(QT_DEBUG) + path += QLatin1String(GAMMARAY_DEBUG_POSTFIX); +#endif +#endif +#endif + lib.setFileName(path); + if (lib.load()) + break; + } + + if (!lib.isLoaded()) { + std::cerr << "Failed to load in-process UI module: " + << qPrintable(lib.errorString()) + << std::endl; + } else { + void (*factory)() = reinterpret_cast(lib.resolve("gammaray_create_inprocess_mainwindow")); + if (!factory) + std::cerr << Q_FUNC_INFO << ' ' << qPrintable(lib.errorString()) << endl; + else + factory(); + } + + IF_DEBUG(cout << "creation done" << endl;) +} + +bool Probe::filterObject(QObject *obj) const +{ + QSet visitedObjects; + int iteration = 0; + QObject *o = obj; + do { + if (iteration > 100) { + // Probably we have a loop in the tree. Do loop detection. + if (visitedObjects.contains(o)) { + std::cerr << "We detected a loop in the object tree for object " << o; + if (!o->objectName().isEmpty()) + std::cerr << " \"" << qPrintable(o->objectName()) << "\""; + std::cerr << " (" << o->metaObject()->className() << ")." << std::endl; + return true; + } + visitedObjects << o; + } + ++iteration; + + if (o == this || o == window() || (qstrncmp(o->metaObject()->className(), "GammaRay::", 10) == 0)) { + return true; + } + o = o->parent(); + } while (o); + return false; +} + +void Probe::registerModel(const QString &objectName, QAbstractItemModel *model) +{ + auto *ms = new RemoteModelServer(objectName, model); + ms->setModel(model); + ObjectBroker::registerModelInternal(objectName, model); +} + +QAbstractItemModel *Probe::objectListModel() const +{ + return m_objectListModel; +} + +const QVector &Probe::allQObjects() const +{ + return m_objectListModel->objects(); +} + +QAbstractItemModel *Probe::objectTreeModel() const +{ + return m_objectTreeModel; +} + +ProblemCollector *Probe::problemCollector() const +{ + return m_problemCollector; +} + +QRecursiveMutex *Probe::objectLock() +{ + return s_lock(); +} + +/* + * We need to handle 4 different cases in here: + * (1) our thread, from ctor: + * - wait until next event-loop re-entry of our thread + * - emit objectCreated if object still valid + * (2) our thread, after ctor: + * - emit objectCreated right away + * (3) other thread, from ctor: + * - wait until next event-loop re-entry in other thread (FIXME: we do not currently do this!!) + * - post information to our thread + * (4) other thread, after ctor: + * - post information to our thread + * - emit objectCreated there right away if object still valid + * + * Pre-conditions: lock may or may not be held already, arbitrary thread + */ +void Probe::objectAdded(QObject *obj, bool fromCtor) +{ + if (obj == nullptr) + return; + QMutexLocker lock(s_lock()); + + // attempt to ignore objects created by GammaRay itself, especially short-lived ones + if (fromCtor && ProbeGuard::insideProbe() && obj->thread() == QThread::currentThread()) + return; + + // ignore objects created when global statics are already getting destroyed (on exit) + if (s_listener.isDestroyed()) + return; + + + if (Execution::hasFastStackTrace() && fromCtor) { + s_listener()->constructionBacktracesForObjects.insert(obj, Execution::stackTrace(32, 2)); // skip 2: this and the hook function calling us + } + + if (!isInitialized()) { + IF_DEBUG(cout + << "objectAdded Before: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + s_listener()->addedBeforeProbeInstance << obj; + return; + } + + if (instance()->filterObject(obj)) { + IF_DEBUG(cout + << "objectAdded Filter: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + return; + } + + if (instance()->m_validObjects.contains(obj)) { + // this happens when we get a child event before the objectAdded call from the ctor + // or when we add an item from addedBeforeProbeInstance who got added already + // due to the add-parent-before-child logic + IF_DEBUG(cout + << "objectAdded Known: " + << hex << obj + << (fromCtor ? " (from ctor)" : "") << endl;) + return; + } + + // make sure we already know the parent + if (obj->parent() && !instance()->m_validObjects.contains(obj->parent())) + objectAdded(obj->parent(), fromCtor); + Q_ASSERT(!obj->parent() || instance()->m_validObjects.contains(obj->parent())); + + instance()->m_validObjects << obj; + + if (!fromCtor && obj->parent() && instance()->isObjectCreationQueued(obj->parent())) { + // when a child event triggers a call to objectAdded while inside the ctor + // the parent is already tracked but it's call to objectFullyConstructed + // was delayed. hence we must do the same for the child for integrity + fromCtor = true; + } + + IF_DEBUG(cout << "objectAdded: " << hex << obj + << (fromCtor ? " (from ctor)" : "") + << ", p: " << obj->parent() << endl;) + + if (fromCtor) + instance()->queueCreatedObject(obj); + else + instance()->objectFullyConstructed(obj); +} + +// pre-conditions: lock may or may not be held already, our thread +void Probe::processQueuedObjectChanges() +{ + QMutexLocker lock(s_lock()); + + IF_DEBUG(cout << Q_FUNC_INFO << " " << m_queuedObjectChanges.size() << endl;) + + // must be called from the main thread via timeout + Q_ASSERT(QThread::currentThread() == thread()); + + const auto queuedObjectChanges = m_queuedObjectChanges; // copy, in case this gets modified while we iterate (which can actually happen) + for (const auto &change : queuedObjectChanges) { + switch (change.type) { + case ObjectChange::Create: + objectFullyConstructed(change.obj); + break; + case ObjectChange::Destroy: + emit objectDestroyed(change.obj); + break; + } + } + + IF_DEBUG(cout << Q_FUNC_INFO << " done" << endl;) + + m_queuedObjectChanges.clear(); + + for (QObject *obj : std::as_const(m_pendingReparents)) { + if (!isValidObject(obj)) + continue; + if (filterObject(obj)) // the move might have put it under a hidden parent + objectRemoved(obj); + else + emit objectReparented(obj); + } + m_pendingReparents.clear(); +} + +// pre-condition: lock is held already, our thread +void Probe::objectFullyConstructed(QObject *obj) +{ + Q_ASSERT(thread() == QThread::currentThread()); + + if (!m_validObjects.contains(obj)) { + // deleted already + IF_DEBUG(cout << "stale fully constructed: " << hex << obj << endl;) + return; + } + + if (filterObject(obj)) { + // when the call was delayed from the ctor construction, + // the parent might not have been set properly yet. hence + // apply the filter again + m_validObjects.remove(obj); + IF_DEBUG(cout << "now filtered fully constructed: " << hex << obj << endl;) + return; + } + + IF_DEBUG(cout << "fully constructed: " << hex << obj << endl;) + + // ensure we know all our ancestors already + for (QObject *parent = obj->parent(); parent; parent = parent->parent()) { + if (!m_validObjects.contains(parent)) { + objectAdded(parent); // will also handle any further ancestors + break; + } + } + Q_ASSERT(!obj->parent() || m_validObjects.contains(obj->parent())); + + m_toolManager->objectAdded(obj); + emit objectCreated(obj); +} + +/* + * We have two cases to consider here: + * (1) our thread: + * - emit objectDestroyed() right away + * (2) other thread: + * - post information to our thread, emit objectDestroyed() there + * + * pre-conditions: arbitrary thread, lock may or may not be held already + */ +void Probe::objectRemoved(QObject *obj) +{ + QMutexLocker lock(s_lock()); + + if (!isInitialized()) { + IF_DEBUG(cout + << "objectRemoved Before: " + << hex << obj + << " have statics: " << s_listener() << endl;) + + if (!s_listener()) + return; + + QVector &addedBefore = s_listener()->addedBeforeProbeInstance; + for (auto it = addedBefore.begin(); it != addedBefore.end();) { + if (*it == obj) + it = addedBefore.erase(it); + else + ++it; + } + return; + } + + IF_DEBUG(cout << "object removed:" << hex << obj << " " << obj->parent() << endl;) + + bool success = instance()->m_validObjects.remove(obj); + if (!success) { + // object was not tracked by the probe, probably a gammaray object + EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); + return; + } + + instance()->purgeChangesForObject(obj); + EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); + + if (instance()->thread() == QThread::currentThread()) + emit instance()->objectDestroyed(obj); + else + instance()->queueDestroyedObject(obj); +} + +void Probe::handleObjectDestroyed(QObject *obj) +{ + objectRemoved(obj); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::queueCreatedObject(QObject *obj) +{ + EXPENSIVE_ASSERT(!isObjectCreationQueued(obj)); + + ObjectChange c; + c.obj = obj; + c.type = ObjectChange::Create; + m_queuedObjectChanges.push_back(c); + notifyQueuedObjectChanges(); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::queueDestroyedObject(QObject *obj) +{ + ObjectChange c; + c.obj = obj; + c.type = ObjectChange::Destroy; + m_queuedObjectChanges.push_back(c); + notifyQueuedObjectChanges(); +} + +// pre-condition: we have the lock, arbitrary thread +bool Probe::isObjectCreationQueued(QObject *obj) const +{ + return std::find_if(m_queuedObjectChanges.begin(), m_queuedObjectChanges.end(), + [obj](const ObjectChange &c) { + return c.obj == obj && c.type == Probe::ObjectChange::Create; + }) + != m_queuedObjectChanges.end(); +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::purgeChangesForObject(QObject *obj) +{ + for (int i = 0; i < m_queuedObjectChanges.size(); ++i) { + if (m_queuedObjectChanges.at(i).obj == obj + && m_queuedObjectChanges.at(i).type == ObjectChange::Create) { + m_queuedObjectChanges.remove(i); + return; + } + } +} + +// pre-condition: we have the lock, arbitrary thread +void Probe::notifyQueuedObjectChanges() +{ + if (m_queueTimer->isActive()) + return; + + if (thread() == QThread::currentThread()) { + m_queueTimer->start(); + } else { + static QMetaMethod m; + if (m.methodIndex() < 0) { + const auto idx = QTimer::staticMetaObject.indexOfMethod("start()"); + Q_ASSERT(idx >= 0); + m = QTimer::staticMetaObject.method(idx); + Q_ASSERT(m.methodIndex() >= 0); + } + m.invoke(m_queueTimer, Qt::QueuedConnection); + } +} + +bool Probe::eventFilter(QObject *receiver, QEvent *event) +{ + if (ProbeGuard::insideProbe() && receiver->thread() == QThread::currentThread()) + return QObject::eventFilter(receiver, event); + + if (event->type() == QEvent::ChildAdded || event->type() == QEvent::ChildRemoved) { + QChildEvent *childEvent = static_cast(event); + QObject *obj = childEvent->child(); + + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(obj); + const bool filtered = filterObject(obj); + + IF_DEBUG(cout << "child event: " << hex << obj << ", p: " << obj->parent() << dec + << ", tracked: " << tracked + << ", filtered: " << filtered + << ", type: " << (childEvent->added() ? "added" : "removed") << endl;) + + if (!filtered && childEvent->added()) { + if (!tracked) { + // was not tracked before, add to all models + // child added events are sent before qt_addObject is called, + // so we assumes this comes from the ctor + objectAdded(obj, true); + } else if (!isObjectCreationQueued(obj) && !isObjectCreationQueued(obj->parent()) && isValidObject(obj->parent())) { + // object is known already, just update the position in the tree + // BUT: only when we did not queue this item before + IF_DEBUG(cout << "update pos: " << hex << obj << endl;) + m_pendingReparents.removeAll(obj); + emit objectReparented(obj); + } else if (!isValidObject(obj->parent())) { + objectAdded(obj->parent()); + m_pendingReparents.push_back(obj); + notifyQueuedObjectChanges(); + } + } else if (tracked) { + // defer processing this until we know its final location + m_pendingReparents.push_back(obj); + notifyQueuedObjectChanges(); + } + } + + // widget only unfortunately, but more precise than ChildAdded/Removed... + if (event->type() == QEvent::ParentChange) { + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(receiver); + const bool filtered = filterObject(receiver); + const bool parentTracked = m_validObjects.contains(receiver->parent()); + + if (!filtered && tracked && !isObjectCreationQueued(receiver) + && !isObjectCreationQueued(receiver->parent()) && parentTracked) { + m_pendingReparents.removeAll(receiver); + emit objectReparented(receiver); + } else if (!parentTracked) { + objectAdded(receiver->parent()); + m_pendingReparents.push_back(receiver); + notifyQueuedObjectChanges(); + } + } + + // we have no preloading hooks, so recover all objects we see + if (needsObjectDiscovery() && event->type() != QEvent::ChildAdded + && event->type() != QEvent::ChildRemoved + && event->type() != QEvent::ParentChange // already handled above + && event->type() != QEvent::Destroy + && event->type() != QEvent::WinIdChange // unsafe since emitted from dtors + && !filterObject(receiver)) { + QMutexLocker lock(s_lock()); + const bool tracked = m_validObjects.contains(receiver); + if (!tracked) + discoverObject(receiver); + } + + // filters provided by plugins + if (!filterObject(receiver)) { + for (QObject *filter : std::as_const(m_globalEventFilters)) { + filter->eventFilter(receiver, event); + } + } + + return QObject::eventFilter(receiver, event); +} + +void Probe::findExistingObjects() +{ + discoverObject(QCoreApplication::instance()); + + if (auto guiApp = qobject_cast(QCoreApplication::instance())) { + foreach (auto window, guiApp->allWindows()) { + discoverObject(window); + } + } +} + +void Probe::discoverObject(QObject *object) +{ + if (!object) + return; + + QMutexLocker lock(s_lock()); + if (m_validObjects.contains(object)) + return; + + objectAdded(object); + + // foreach (QObject *child, object->children()) { + // discoverObject(child); + // } + + const auto children = object->children(); + for (QObject *child : children) { + if (!child) continue; + if (m_validObjects.contains(child)) continue; + discoverObject(child); + } +} + +void Probe::installGlobalEventFilter(QObject *filter) +{ + Q_ASSERT(!m_globalEventFilters.contains(filter)); + m_globalEventFilters.push_back(filter); +} + +bool Probe::needsObjectDiscovery() +{ + return s_listener()->trackDestroyed; +} + +bool Probe::hasReliableObjectTracking() +{ + return true; // qHooks available, which works independent of the injector used +} + +void Probe::selectObject(QObject *object, const QPoint &pos) +{ + const auto tools = m_toolManager->toolsForObject(object); + m_toolManager->selectTool(tools.value(0)); + emit objectSelected(object, pos); +} + +void Probe::selectObject(QObject *object, const QString &toolId, const QPoint &pos) +{ + if (!m_toolManager->hasTool(toolId)) { + std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; + return; + } + + m_toolManager->selectTool(toolId); + emit objectSelected(object, pos); +} + +void Probe::selectObject(void *object, const QString &typeName) +{ + const auto tools = m_toolManager->toolsForObject(object, typeName); + const QString toolId = tools.value(0); + + if (!m_toolManager->hasTool(toolId)) { + std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; + return; + } + + m_toolManager->selectTool(tools.value(0)); + emit nonQObjectSelected(object, typeName); +} + +void Probe::markObjectAsFavorite(QObject *object) +{ + { + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(object)) + return; // deleted in the slot + } + + Q_EMIT objectFavorited(object); +} + +void Probe::removeObjectAsFavorite(QObject *object) +{ + { + QMutexLocker locker(Probe::objectLock()); + if (!Probe::instance()->isValidObject(object)) + return; // deleted in the slot + } + Q_EMIT objectUnfavorited(object); +} + +void Probe::registerSignalSpyCallbackSet(const SignalSpyCallbackSet &callbacks) +{ + if (callbacks.isNull()) + return; + m_signalSpyCallbacks.push_back(callbacks); + setupSignalSpyCallbacks(); +} + +void Probe::setupSignalSpyCallbacks() +{ + // memory management is with us for Qt >= 5.14, therefore static here! + static QSignalSpyCallbackSet cbs = { nullptr, nullptr, nullptr, nullptr }; + foreach (const auto &it, m_signalSpyCallbacks) { + if (it.signalBeginCallback) + cbs.signal_begin_callback = signal_begin_callback; + if (it.signalEndCallback) + cbs.signal_end_callback = signal_end_callback; + if (it.slotBeginCallback) + cbs.slot_begin_callback = slot_begin_callback; + if (it.slotEndCallback) + cbs.slot_end_callback = slot_end_callback; + } + qt_register_signal_spy_callbacks(&cbs); +} + +template +void Probe::executeSignalCallback(const Func &func) +{ + std::for_each(instance()->m_signalSpyCallbacks.constBegin(), + instance()->m_signalSpyCallbacks.constEnd(), + func); +} + +SourceLocation Probe::objectCreationSourceLocation(const QObject *object) +{ + QObject *key = const_cast(object); + if (!s_listener()->constructionBacktracesForObjects.contains(key)) { + IF_DEBUG(std::cout << "No backtrace for object available" << object << "." << std::endl;) + return SourceLocation(); + } + + const auto &st = s_listener()->constructionBacktracesForObjects.value(key); + int distanceToQObject = 0; + + const QMetaObject *metaObject = object->metaObject(); + while (metaObject && metaObject != &QObject::staticMetaObject) { + distanceToQObject++; + metaObject = metaObject->superClass(); + } + + const auto frame = Execution::resolveOne(st, distanceToQObject + 1); + return frame.location; +} + +Execution::Trace Probe::objectCreationStackTrace(QObject *object) +{ + return s_listener()->constructionBacktracesForObjects.value(object); +} diff --git a/.history/plugins/quickinspector/quickinspector_20260413092956.cpp b/.history/plugins/quickinspector/quickinspector_20260413092956.cpp new file mode 100644 index 0000000000..8a7a7da8be --- /dev/null +++ b/.history/plugins/quickinspector/quickinspector_20260413092956.cpp @@ -0,0 +1,1190 @@ +/* + quickinspector.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ + +#include "quickinspector.h" + +#include "quickanchorspropertyadaptor.h" +#include "quickitemmodel.h" +#include "quickscenegraphmodel.h" +#include "quickscreengrabber.h" +#include "quickpaintanalyzerextension.h" +#include "geometryextension/sggeometryextension.h" +#include "materialextension/materialextension.h" +#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" +#include "textureextension/qsgtexturegrabber.h" +#include "textureextension/textureextension.h" + +#include "quickimplicitbindingdependencyprovider.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include + +Q_DECLARE_METATYPE(QQmlError) + +Q_DECLARE_METATYPE(QQuickItem::Flags) +Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) +Q_DECLARE_METATYPE(QSGNode *) +Q_DECLARE_METATYPE(QSGBasicGeometryNode *) +Q_DECLARE_METATYPE(QSGGeometryNode *) +Q_DECLARE_METATYPE(QSGClipNode *) +Q_DECLARE_METATYPE(QSGTransformNode *) +Q_DECLARE_METATYPE(QSGRootNode *) +Q_DECLARE_METATYPE(QSGOpacityNode *) +Q_DECLARE_METATYPE(QSGNode::Flags) +Q_DECLARE_METATYPE(QSGNode::DirtyState) +Q_DECLARE_METATYPE(QSGGeometry *) +Q_DECLARE_METATYPE(QMatrix4x4 *) +Q_DECLARE_METATYPE(const QMatrix4x4 *) +Q_DECLARE_METATYPE(const QSGClipNode *) +Q_DECLARE_METATYPE(const QSGGeometry *) +Q_DECLARE_METATYPE(QSGMaterial *) +Q_DECLARE_METATYPE(QSGMaterial::Flags) +Q_DECLARE_METATYPE(QSGTexture::WrapMode) +Q_DECLARE_METATYPE(QSGTexture::Filtering) +Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) +Q_DECLARE_METATYPE(QSGRenderNode *) +Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) +Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) +Q_DECLARE_METATYPE(QSGRendererInterface *) +Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) + +using namespace GammaRay; + +#define E(x) \ + { \ + QQuickItem::x, #x \ + } +static const MetaEnum::Value qqitem_flag_table[] = { + E(ItemClipsChildrenToShape), + E(ItemAcceptsInputMethod), + E(ItemIsFocusScope), + E(ItemHasContents), + E(ItemAcceptsDrops) +}; +#undef E + +static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) +{ + QStringList list; + if (hints & QQuickPaintedItem::FastFBOResizing) + list << QStringLiteral("FastFBOResizing"); + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGNode::x, #x \ + } +static const MetaEnum::Value qsg_node_flag_table[] = { + E(OwnedByParent), + E(UsePreprocess), + E(OwnsGeometry), + E(OwnsMaterial), + E(OwnsOpaqueMaterial) +}; + +static const MetaEnum::Value qsg_node_dirtystate_table[] = { + E(DirtySubtreeBlocked), + E(DirtyMatrix), + E(DirtyNodeAdded), + E(DirtyNodeRemoved), + E(DirtyGeometry), + E(DirtyMaterial), + E(DirtyOpacity), + E(DirtyForceUpdate), + E(DirtyUsePreprocess), + E(DirtyPropagationMask) +}; +#undef E + +static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) +{ + QStringList list; +#define F(f) \ + if (flags & QSGMaterial::f) \ + list.push_back(QStringLiteral(#f)); + F(Blending) + F(RequiresDeterminant) + F(RequiresFullMatrixExceptTranslate) + F(RequiresFullMatrix) + F(NoBatching) +#undef F + + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGTexture::x, #x \ + } +static const MetaEnum::Value qsg_texture_anisotropy_table[] = { + E(AnisotropyNone), + E(Anisotropy2x), + E(Anisotropy4x), + E(Anisotropy8x), + E(Anisotropy16x) +}; + +static const MetaEnum::Value qsg_texture_filtering_table[] = { + E(None), + E(Nearest), + E(Linear) +}; + +static const MetaEnum::Value qsg_texture_wrapmode_table[] = { + E(Repeat), + E(ClampToEdge), + E(MirroredRepeat) +}; + +#undef E + +static bool itemHasContents(QQuickItem *item) +{ + return item->flags().testFlag(QQuickItem::ItemHasContents); +} + +static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) +{ + return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); +} + +static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) +{ + switch (customRenderMode) { + case QuickInspectorInterface::VisualizeClipping: + return QByteArray("clip"); + case QuickInspectorInterface::VisualizeOverdraw: + return QByteArray("overdraw"); + case QuickInspectorInterface::VisualizeBatches: + return QByteArray("batches"); + case QuickInspectorInterface::VisualizeChanges: + return QByteArray("changes"); + case QuickInspectorInterface::VisualizeTraces: + case QuickInspectorInterface::NormalRendering: + break; + } + return QByteArray(); +} + +QMutex RenderModeRequest::mutex; + +RenderModeRequest::RenderModeRequest(QObject *parent) + : QObject(parent) + , mode(QuickInspectorInterface::NormalRendering) +{ +} + +RenderModeRequest::~RenderModeRequest() +{ + QMutexLocker lock(&mutex); + + window.clear(); + + if (connection) + disconnect(connection); +} + +void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) +{ + if (toWindow) { + QMutexLocker lock(&mutex); + // Qt does some performance optimizations that break custom render modes. + // Thus the optimizations are only applied if there is no custom render mode set. + // So we need to make the scenegraph recheck whether a custom render mode is set. + // We do this by simply cleaning the scene graph which will recreate the renderer. + // We need however to do that at the proper time from the gui thread. + + if (!connection || (mode != customRenderMode || window != toWindow)) { + if (connection) + disconnect(connection); + mode = customRenderMode; + window = toWindow; + connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); + // trigger window update so afterRendering is emitted + QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); + } + } +} + +void RenderModeRequest::apply() +{ + QMutexLocker lock(&mutex); + + if (connection) + disconnect(connection); + + if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + return; + + if (window) { + const QByteArray mode = renderModeToString(RenderModeRequest::mode); + QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); + QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { + emit aboutToCleanSceneGraph(); + QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); + winPriv->visualizationMode = mode; + emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); + } + + QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); +} + +void RenderModeRequest::preFinished() +{ + QMutexLocker lock(&mutex); + + if (window) + window->update(); + + emit finished(); +} + +QuickInspector::QuickInspector(Probe *probe, QObject *parent) + : QuickInspectorInterface(parent) + , m_probe(probe) + , m_currentSgNode(nullptr) + , m_itemModel(new QuickItemModel(this)) + , m_sgModel(new QuickSceneGraphModel(this)) + , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), + this)) + , m_sgPropertyController(new PropertyController(QStringLiteral( + "com.kdab.GammaRay.QuickSceneGraph"), + this)) + , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) + , m_pendingRenderMode(new RenderModeRequest(this)) + , m_renderMode(QuickInspectorInterface::NormalRendering) + , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) + , m_slowDownEnabled(false) +{ + registerMetaTypes(); + registerVariantHandlers(); + probe->installGlobalEventFilter(this); + + QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); + windowModel->setSourceModel(probe->objectListModel()); + QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); + proxy->setSourceModel(windowModel); + m_windowModel = proxy; + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); + + auto filterProxy = new ServerProxyModel(this); + filterProxy->setSourceModel(m_itemModel); + filterProxy->addRole(ObjectModel::ObjectIdRole); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); + + if (m_probe->needsObjectDiscovery()) { + connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); + } + + connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); + connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); + connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); + connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); + connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); + connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); + + m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); + connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::itemSelectionChanged); + + auto sgFilterProxy = new ServerProxyModel(this); + sgFilterProxy->setSourceModel(m_sgModel); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); + + m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); + connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::sgSelectionChanged); + connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); + + connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); + connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); + connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); + connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); + connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); + connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); + +#ifndef QT_NO_OPENGL + auto texGrab = new QSGTextureGrabber(this); + connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); +#endif + + connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { + if (m_overlay) + m_overlay->placeOn(ItemOrLayoutFacade()); + }); + + ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", + "QtQuick Item check", + "Warns about items that are visible but out of view.", + &QuickInspector::scanForProblems); + + // needs to be last, extensions require some of the above to be set up correctly + registerPCExtensions(); +} + +QuickInspector::~QuickInspector() +{ + if (m_overlay) { + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + } +} + +void QuickInspector::selectWindow(int index) +{ + const QModelIndex mi = m_windowModel->index(index, 0); + QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); + selectWindow(window); +} + +void QuickInspector::selectWindow(QQuickWindow *window) +{ + if (m_window == window) { + return; + } + + if (m_window) { + const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; + + if (!mode.isEmpty()) { + auto reset = new RenderModeRequest(m_window); + connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); + reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); + } + } + + m_window = window; + m_itemModel->setWindow(window); + m_sgModel->setWindow(window); + m_remoteView->setEventReceiver(m_window); + m_remoteView->resetView(); + recreateOverlay(); + + if (m_window) { + // make sure we have selected something for the property editor to not be entirely empty + selectItem(m_window->contentItem()); + m_window->update(); + } + + checkFeatures(); + + if (m_window) + setCustomRenderMode(m_renderMode); +} + +void QuickInspector::selectItem(QQuickItem *item) +{ + const QAbstractItemModel *model = m_itemSelectionModel->model(); + Model::used(model); + Model::used(m_sgSelectionModel->model()); + + const QModelIndexList indexList = model->match(model->index(0, 0), + ObjectModel::ObjectRole, + QVariant::fromValue(item), 1, + Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_itemSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::selectSGNode(QSGNode *node) +{ + const QAbstractItemModel *model = m_sgSelectionModel->model(); + Model::used(model); + + const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, + QVariant::fromValue( + node), + 1, + Qt::MatchExactly | Qt::MatchRecursive + | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_sgSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::qObjectSelected(QObject *object) +{ + if (auto item = qobject_cast(object)) + selectItem(item); + else if (auto window = qobject_cast(object)) + selectWindow(window); +} + +void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) +{ + auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); + if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) + selectSGNode(reinterpret_cast(object)); +} + +void QuickInspector::objectCreated(QObject *object) +{ + if (QQuickWindow *window = qobject_cast(object)) { + if (QQuickView *view = qobject_cast(object)) { + m_probe->discoverObject(view->engine()); + } else { + QQmlContext *context = QQmlEngine::contextForObject(window); + QQmlEngine *engine = context ? context->engine() : nullptr; + + if (!engine) { + engine = qmlEngine(window->contentItem()->childItems().value(0)); + } + + m_probe->discoverObject(engine); + } + } +} + +void QuickInspector::recreateOverlay() +{ + ProbeGuard guard; + if (m_overlay) + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + + m_overlay = AbstractScreenGrabber::get(m_window); + if (!m_overlay) + return; + + connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); + // the target application might have destroyed the overlay widget + // (e.g. because the parent of the overlay got destroyed). + // just recreate a new one in this case + connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? + // It is for the widget inspector, but for qt quick? + connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); + m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); + + m_remoteView->setGrabberReady(true); +} + +void QuickInspector::aboutToCleanSceneGraph() +{ + m_sgModel->setWindow(nullptr); + m_currentSgNode = nullptr; + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::sceneGraphCleanedUp() +{ + m_sgModel->setWindow(m_window); +} + +void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) +{ + if (!m_window) + return; + RemoteViewFrame frame; + frame.setImage(grabbedFrame.image, grabbedFrame.transform); + frame.setSceneRect(grabbedFrame.itemsGeometryRect); + frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); + if (m_overlay && m_overlay->settings().componentsTraces) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); + else if (!grabbedFrame.itemsGeometry.isEmpty()) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); + m_remoteView->sendFrame(frame); +} + +void QuickInspector::slotGrabWindow() +{ + if (!m_remoteView->isActive() || !m_window) + return; + + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + if (m_overlay) { + m_overlay->requestGrabWindow(m_remoteView->userViewport()); + } +} + +void QuickInspector::setCustomRenderMode( + GammaRay::QuickInspectorInterface::RenderMode customRenderMode) +{ + + m_renderMode = customRenderMode; + + m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); + + const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; + if (m_overlay && m_overlay->settings().componentsTraces != tracing) { + auto settings = m_overlay->settings(); + settings.componentsTraces = tracing; + setOverlaySettings(settings); + } +} + +void QuickInspector::checkFeatures() +{ + Features f; + if (!m_window) { + emit features(f); + return; + } + + if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) + f = AllCustomRenderModes; + else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) + f = AnalyzePainting; + + emit features(f); +} + +void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) +{ + if (!m_overlay) { + emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. + return; + } + + m_overlay->setSettings(settings); + emit overlaySettings(m_overlay->settings()); +} + +void QuickInspector::checkOverlaySettings() +{ + emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); +} + +class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer +{ +public: + using QSGAbstractSoftwareRenderer::buildRenderList; + using QSGAbstractSoftwareRenderer::optimizeRenderList; + using QSGAbstractSoftwareRenderer::renderNodes; +}; + +#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) +// keep it working in UBSAN +__attribute__((no_sanitize("vptr"))) +#endif +void QuickInspector::analyzePainting() +{ + if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) + return; + + m_paintAnalyzer->beginAnalyzePainting(); + m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); + { + auto w = QQuickWindowPrivate::get(m_window); + auto renderer = static_cast(w->renderer); + + // this replicates what QSGSoftwareRender is doing + QPainter painter(m_paintAnalyzer->paintDevice()); + painter.setRenderHint(QPainter::Antialiasing); + auto rc = static_cast(w->renderer->context()); + auto prevPainter = rc->m_activePainter; + rc->m_activePainter = &painter; + renderer->markDirty(); + renderer->buildRenderList(); + renderer->optimizeRenderList(); + renderer->renderNodes(&painter); + + rc->m_activePainter = prevPainter; + } + m_paintAnalyzer->endAnalyzePainting(); +} + +void QuickInspector::checkSlowMode() +{ + // We can't check that for now as there is no getter for the property... + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::setSlowMode(bool slow) +{ + if (m_slowDownEnabled == slow) + return; + + static QHash connections; + + m_slowDownEnabled = slow; + + for (int i = 0; i < m_windowModel->rowCount(); ++i) { + const QModelIndex index = m_windowModel->index(i, 0); + QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); + auto it = connections.find(window); + + if (it == connections.end()) { + connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { + auto it = connections.find(window); +#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) + QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); +#else + QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); +#endif + QObject::disconnect(it.value()); + connections.erase(it); }, Qt::DirectConnection)); + } + } + + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::itemSelectionChanged(const QItemSelection &selection) +{ + const QModelIndex index = selection.value(0).topLeft(); + m_currentItem = index.data(ObjectModel::ObjectRole).value(); + m_itemPropertyController->setObject(m_currentItem); + + // It might be that a sg-node is already selected that belongs to this item, but isn't the root + // node of the Item. In this case we don't want to overwrite that selection. + if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { + m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); + const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); + auto proxy = qobject_cast(m_sgSelectionModel->model()); + m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); + } + + if (m_overlay) + m_overlay->placeOn(m_currentItem.data()); +} + +void QuickInspector::sgSelectionChanged(const QItemSelection &selection) +{ + if (selection.isEmpty()) + return; + + const QModelIndex index = selection.first().topLeft(); + m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); + if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) + return; // Apparently the node has been deleted meanwhile, so don't access it. + + void *obj = m_currentSgNode; + auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); + m_sgPropertyController->setObject(m_currentSgNode, mo->className()); + + m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); + selectItem(m_currentItem); +} + +void QuickInspector::sgNodeDeleted(QSGNode *node) +{ + if (m_currentSgNode == node) + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) +{ + if (!m_window) + return; + + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); + + if (!objects.isEmpty()) { + emit elementsAtReceived(objects, bestCandidate); + } +} + +void QuickInspector::pickElementId(const GammaRay::ObjectId &id) +{ + QQuickItem *item = id.asQObjectType(); + if (item) + m_probe->selectObject(item); +} + +QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const +{ + auto rect = parent->childrenRect(); + + const auto childItems = parent->childItems(); + for (const auto child : childItems) { + auto childRect = child->childrenRect(); + + // Get Global positon of childRect + QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); + + // Convert global position to local coordinates of the parent object + QPointF localChildPos = parent->mapFromScene(childGlobalPos); + + // Adjust childRect to be in local coordinates of the parent object + childRect.moveTopLeft(localChildPos.toPoint()); + + // Adding the childRect to the rect + rect = rect.united(childRect); + } + + return rect; +} + +ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, + GammaRay::RemoteViewInterface::RequestMode mode, + int &bestCandidate, bool parentIsGoodCandidate) const +{ + Q_ASSERT(parent); + ObjectIds objects; + + bestCandidate = -1; + if (parentIsGoodCandidate) { + // inherit the parent item opacity when looking for a good candidate item + // i.e. QQuickItem::isVisible is taking the parent into account already, but + // the opacity doesn't - we have to do this manually + // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem + // at least seems to not have this flag set. + parentIsGoodCandidate = isGoodCandidateItem(parent, true); + } + + auto childItems = parent->childItems(); + std::stable_sort(childItems.begin(), childItems.end(), + [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); + + for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order + const auto child = childItems.at(i); + const auto requestedPoint = parent->mapToItem(child, pos); + if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { + const int count = objects.size(); + int bc; // possibly better candidate among subChildren + objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); + + if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { + bestCandidate = count + bc; + } + } + + if (child->contains(requestedPoint)) { + if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { + bestCandidate = objects.size(); + } + objects << ObjectId(child); + } + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + break; + } + } + + if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { + bestCandidate = objects.size(); + } + + objects << ObjectId(parent); + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + objects = ObjectIds() << objects[bestCandidate]; + bestCandidate = 0; + } + + return objects; +} + + +void QuickInspector::scanForProblems() +{ + const QVector &allObjects = Probe::instance()->allQObjects(); + + QMutexLocker lock(Probe::objectLock()); + for (QObject *obj : allObjects) { + QQuickItem *item; + if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) + continue; + + QQuickItem *ancestor = item->parentItem(); + auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); + + while (ancestor && item->window() && ancestor != item->window()->contentItem()) { + if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { + auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); + + if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { + Problem p; + p.severity = Problem::Info; + p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); + p.object = ObjectId(item); + p.locations.push_back(ObjectDataProvider::creationLocation(item)); + p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); + p.findingCategory = Problem::Scan; + ProblemCollector::addProblem(p); + break; + } + } + ancestor = ancestor->parentItem(); + } + } +} + +bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEv = static_cast(event); + if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + QQuickWindow *window = qobject_cast(receiver); + if (window && window->contentItem()) { + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), + RemoteViewInterface::RequestBest, bestCandidate); + m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); + } + } + } + + return QObject::eventFilter(receiver, event); +} + +void QuickInspector::registerMetaTypes() +{ + MetaObject *mo = nullptr; + MO_ADD_METAOBJECT1(QQuickWindow, QWindow); + + MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); + MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); + MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); + +#ifndef QT_NO_OPENGL + MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); +#endif + + MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); + MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); + MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); + + MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); + + MO_ADD_METAOBJECT0(QSGRendererInterface); + MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); + + MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); + MO_ADD_PROPERTY_RO(QQuickView, engine); + MO_ADD_PROPERTY_RO(QQuickView, errors); + MO_ADD_PROPERTY_RO(QQuickView, initialSize); + MO_ADD_PROPERTY_RO(QQuickView, rootContext); + MO_ADD_PROPERTY_RO(QQuickView, rootObject); + + MO_ADD_METAOBJECT1(QQuickItem, QObject); + MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); + MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); + MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); + MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); + MO_ADD_PROPERTY(QQuickItem, flags, setFlags); + MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); + MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); + MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); + MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); + MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); + MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); + MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); + MO_ADD_PROPERTY_RO(QQuickItem, window); + + MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); + MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); + MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); + MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); + MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); + + MO_ADD_METAOBJECT1(QSGTexture, QObject); + MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); + MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); + MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); + MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); + MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); + // crashes without a current GL context + // MO_ADD_PROPERTY_RO(QSGTexture, textureId); + MO_ADD_PROPERTY_RO(QSGTexture, textureSize); + MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); + + MO_ADD_METAOBJECT0(QSGNode); + MO_ADD_PROPERTY_RO(QSGNode, parent); + MO_ADD_PROPERTY_RO(QSGNode, childCount); + MO_ADD_PROPERTY_RO(QSGNode, flags); + MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT +#endif + + MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); + MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); + + MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); + MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); + MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); + + MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); + MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); + + MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); + MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); + MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); + + MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); + + MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); + MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); + MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); + + MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); + MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); + MO_ADD_PROPERTY_RO(QSGRenderNode, flags); + MO_ADD_PROPERTY_RO(QSGRenderNode, rect); + MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); + MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); + MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); + + MO_ADD_METAOBJECT0(QSGMaterial); + MO_ADD_PROPERTY_RO(QSGMaterial, flags); + + MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); + + MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); + MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); + + MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); + +#ifndef QT_NO_OPENGL + MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); + + MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); + + MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); +#endif +} + +#define E(x) \ + { \ + QSGRendererInterface::x, #x \ + } +static const MetaEnum::Value qsg_graphics_api_table[] = { + E(Unknown), + E(Software), + E(OpenGL), + E(OpenVG), + E(Direct3D11), + E(Vulkan), + E(Metal), +}; + +static const MetaEnum::Value qsg_shader_compilation_type_table[] = { + E(RuntimeCompilation), + E(OfflineCompilation) +}; + +static const MetaEnum::Value qsg_shader_source_type_table[] = { + E(ShaderSourceString), + E(ShaderSourceFile), + E(ShaderByteCode) +}; + +static const MetaEnum::Value qsg_shader_type_table[] = { + E(UnknownShadingLanguage), + E(GLSL), + E(HLSL) +}; +#undef E + +#define E(x) \ + { \ + QSGRenderNode::x, #x \ + } +static const MetaEnum::Value render_node_state_flags_table[] = { + E(DepthState), + E(StencilState), + E(ScissorState), + E(ColorState), + E(BlendState), + E(CullState), + E(CullState), + E(ViewportState), + E(RenderTargetState) +}; + +static const MetaEnum::Value render_node_rendering_flags_table[] = { + E(BoundedRectRendering), + E(DepthAwareRendering), + E(OpaqueRendering) +}; +#undef E + +static QString anchorLineToString(const QQuickAnchorLine &line) +{ + if (!line.item + || line.anchorLine == QQuickAnchors::InvalidAnchor) { + return QStringLiteral(""); + } + QString s = Util::shortDisplayString(line.item); + switch (line.anchorLine) { + case QQuickAnchors::LeftAnchor: + return s + QStringLiteral(".left"); + case QQuickAnchors::RightAnchor: + return s + QStringLiteral(".right"); + case QQuickAnchors::TopAnchor: + return s + QStringLiteral(".top"); + case QQuickAnchors::BottomAnchor: + return s + QStringLiteral(".bottom"); + case QQuickAnchors::HCenterAnchor: + return s + QStringLiteral(".horizontalCenter"); + case QQuickAnchors::VCenterAnchor: + return s + QStringLiteral(".verticalCenter"); + case QQuickAnchors::BaselineAnchor: + return s + QStringLiteral(".baseline"); + default: + break; + } + return s; +} + +void QuickInspector::registerVariantHandlers() +{ + ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); + ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); + ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); + ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); + ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); + ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); + + VariantHandler::registerStringConverter( + qQuickPaintedItemPerformanceHintsToString); + VariantHandler::registerStringConverter(anchorLineToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(qsgMaterialFlagsToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); + + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); +} + +void QuickInspector::registerPCExtensions() +{ +#ifndef QT_NO_OPENGL + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + + PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); +#endif + PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); + PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); + + BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); +} diff --git a/.history/plugins/quickinspector/quickinspector_20260413093758.cpp b/.history/plugins/quickinspector/quickinspector_20260413093758.cpp new file mode 100644 index 0000000000..1c2dc6abd8 --- /dev/null +++ b/.history/plugins/quickinspector/quickinspector_20260413093758.cpp @@ -0,0 +1,1201 @@ +/* + quickinspector.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ + +#include "quickinspector.h" + +#include "quickanchorspropertyadaptor.h" +#include "quickitemmodel.h" +#include "quickscenegraphmodel.h" +#include "quickscreengrabber.h" +#include "quickpaintanalyzerextension.h" +#include "geometryextension/sggeometryextension.h" +#include "materialextension/materialextension.h" +#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" +#include "textureextension/qsgtexturegrabber.h" +#include "textureextension/textureextension.h" + +#include "quickimplicitbindingdependencyprovider.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include + +Q_DECLARE_METATYPE(QQmlError) + +Q_DECLARE_METATYPE(QQuickItem::Flags) +Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) +Q_DECLARE_METATYPE(QSGNode *) +Q_DECLARE_METATYPE(QSGBasicGeometryNode *) +Q_DECLARE_METATYPE(QSGGeometryNode *) +Q_DECLARE_METATYPE(QSGClipNode *) +Q_DECLARE_METATYPE(QSGTransformNode *) +Q_DECLARE_METATYPE(QSGRootNode *) +Q_DECLARE_METATYPE(QSGOpacityNode *) +Q_DECLARE_METATYPE(QSGNode::Flags) +Q_DECLARE_METATYPE(QSGNode::DirtyState) +Q_DECLARE_METATYPE(QSGGeometry *) +Q_DECLARE_METATYPE(QMatrix4x4 *) +Q_DECLARE_METATYPE(const QMatrix4x4 *) +Q_DECLARE_METATYPE(const QSGClipNode *) +Q_DECLARE_METATYPE(const QSGGeometry *) +Q_DECLARE_METATYPE(QSGMaterial *) +Q_DECLARE_METATYPE(QSGMaterial::Flags) +Q_DECLARE_METATYPE(QSGTexture::WrapMode) +Q_DECLARE_METATYPE(QSGTexture::Filtering) +Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) +Q_DECLARE_METATYPE(QSGRenderNode *) +Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) +Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) +Q_DECLARE_METATYPE(QSGRendererInterface *) +Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) + +using namespace GammaRay; + +#define E(x) \ + { \ + QQuickItem::x, #x \ + } +static const MetaEnum::Value qqitem_flag_table[] = { + E(ItemClipsChildrenToShape), + E(ItemAcceptsInputMethod), + E(ItemIsFocusScope), + E(ItemHasContents), + E(ItemAcceptsDrops) +}; +#undef E + +static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) +{ + QStringList list; + if (hints & QQuickPaintedItem::FastFBOResizing) + list << QStringLiteral("FastFBOResizing"); + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGNode::x, #x \ + } +static const MetaEnum::Value qsg_node_flag_table[] = { + E(OwnedByParent), + E(UsePreprocess), + E(OwnsGeometry), + E(OwnsMaterial), + E(OwnsOpaqueMaterial) +}; + +static const MetaEnum::Value qsg_node_dirtystate_table[] = { + E(DirtySubtreeBlocked), + E(DirtyMatrix), + E(DirtyNodeAdded), + E(DirtyNodeRemoved), + E(DirtyGeometry), + E(DirtyMaterial), + E(DirtyOpacity), + E(DirtyForceUpdate), + E(DirtyUsePreprocess), + E(DirtyPropagationMask) +}; +#undef E + +static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) +{ + QStringList list; +#define F(f) \ + if (flags & QSGMaterial::f) \ + list.push_back(QStringLiteral(#f)); + F(Blending) + F(RequiresDeterminant) + F(RequiresFullMatrixExceptTranslate) + F(RequiresFullMatrix) + F(NoBatching) +#undef F + + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGTexture::x, #x \ + } +static const MetaEnum::Value qsg_texture_anisotropy_table[] = { + E(AnisotropyNone), + E(Anisotropy2x), + E(Anisotropy4x), + E(Anisotropy8x), + E(Anisotropy16x) +}; + +static const MetaEnum::Value qsg_texture_filtering_table[] = { + E(None), + E(Nearest), + E(Linear) +}; + +static const MetaEnum::Value qsg_texture_wrapmode_table[] = { + E(Repeat), + E(ClampToEdge), + E(MirroredRepeat) +}; + +#undef E + +static bool itemHasContents(QQuickItem *item) +{ + return item->flags().testFlag(QQuickItem::ItemHasContents); +} + +static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) +{ + return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); +} + +static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) +{ + switch (customRenderMode) { + case QuickInspectorInterface::VisualizeClipping: + return QByteArray("clip"); + case QuickInspectorInterface::VisualizeOverdraw: + return QByteArray("overdraw"); + case QuickInspectorInterface::VisualizeBatches: + return QByteArray("batches"); + case QuickInspectorInterface::VisualizeChanges: + return QByteArray("changes"); + case QuickInspectorInterface::VisualizeTraces: + case QuickInspectorInterface::NormalRendering: + break; + } + return QByteArray(); +} + +QMutex RenderModeRequest::mutex; + +RenderModeRequest::RenderModeRequest(QObject *parent) + : QObject(parent) + , mode(QuickInspectorInterface::NormalRendering) +{ +} + +RenderModeRequest::~RenderModeRequest() +{ + QMutexLocker lock(&mutex); + + window.clear(); + + if (connection) + disconnect(connection); +} + +void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) +{ + if (toWindow) { + QMutexLocker lock(&mutex); + // Qt does some performance optimizations that break custom render modes. + // Thus the optimizations are only applied if there is no custom render mode set. + // So we need to make the scenegraph recheck whether a custom render mode is set. + // We do this by simply cleaning the scene graph which will recreate the renderer. + // We need however to do that at the proper time from the gui thread. + + if (!connection || (mode != customRenderMode || window != toWindow)) { + if (connection) + disconnect(connection); + mode = customRenderMode; + window = toWindow; + connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); + // trigger window update so afterRendering is emitted + QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); + } + } +} + +void RenderModeRequest::apply() +{ + QMutexLocker lock(&mutex); + + if (connection) + disconnect(connection); + + if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + return; + + if (window) { + const QByteArray mode = renderModeToString(RenderModeRequest::mode); + QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); + QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { + emit aboutToCleanSceneGraph(); + QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); + winPriv->visualizationMode = mode; + emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); + } + + QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); +} + +void RenderModeRequest::preFinished() +{ + QMutexLocker lock(&mutex); + + if (window) + window->update(); + + emit finished(); +} + +QuickInspector::QuickInspector(Probe *probe, QObject *parent) + : QuickInspectorInterface(parent) + , m_probe(probe) + , m_currentSgNode(nullptr) + , m_itemModel(new QuickItemModel(this)) + , m_sgModel(new QuickSceneGraphModel(this)) + , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), + this)) + , m_sgPropertyController(new PropertyController(QStringLiteral( + "com.kdab.GammaRay.QuickSceneGraph"), + this)) + , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) + , m_pendingRenderMode(new RenderModeRequest(this)) + , m_renderMode(QuickInspectorInterface::NormalRendering) + , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) + , m_slowDownEnabled(false) +{ + registerMetaTypes(); + registerVariantHandlers(); + probe->installGlobalEventFilter(this); + + QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); + windowModel->setSourceModel(probe->objectListModel()); + QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); + proxy->setSourceModel(windowModel); + m_windowModel = proxy; + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); + + auto filterProxy = new ServerProxyModel(this); + filterProxy->setSourceModel(m_itemModel); + filterProxy->addRole(ObjectModel::ObjectIdRole); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); + + if (m_probe->needsObjectDiscovery()) { + connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); + } + + connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); + connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); + connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); + connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); + connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); + connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); + + m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); + connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::itemSelectionChanged); + + auto sgFilterProxy = new ServerProxyModel(this); + sgFilterProxy->setSourceModel(m_sgModel); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); + + m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); + connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::sgSelectionChanged); + connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); + + connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); + connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); + connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); + connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); + connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); + connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); + +#ifndef QT_NO_OPENGL + auto texGrab = new QSGTextureGrabber(this); + connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); +#endif + + connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { + if (m_overlay) + m_overlay->placeOn(ItemOrLayoutFacade()); + }); + + ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", + "QtQuick Item check", + "Warns about items that are visible but out of view.", + &QuickInspector::scanForProblems); + + // needs to be last, extensions require some of the above to be set up correctly + registerPCExtensions(); +} + +QuickInspector::~QuickInspector() +{ + if (m_overlay) { + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + } +} + +void QuickInspector::selectWindow(int index) +{ + const QModelIndex mi = m_windowModel->index(index, 0); + QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); + selectWindow(window); +} + +void QuickInspector::selectWindow(QQuickWindow *window) +{ + if (m_window == window) { + return; + } + + if (m_window) { + const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; + + if (!mode.isEmpty()) { + auto reset = new RenderModeRequest(m_window); + connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); + reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); + } + } + + m_window = window; + m_itemModel->setWindow(window); + m_sgModel->setWindow(window); + m_remoteView->setEventReceiver(m_window); + m_remoteView->resetView(); + recreateOverlay(); + + if (m_window) { + // make sure we have selected something for the property editor to not be entirely empty + selectItem(m_window->contentItem()); + m_window->update(); + } + + checkFeatures(); + + if (m_window) + setCustomRenderMode(m_renderMode); +} + +void QuickInspector::selectItem(QQuickItem *item) +{ + const QAbstractItemModel *model = m_itemSelectionModel->model(); + Model::used(model); + Model::used(m_sgSelectionModel->model()); + + const QModelIndexList indexList = model->match(model->index(0, 0), + ObjectModel::ObjectRole, + QVariant::fromValue(item), 1, + Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_itemSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::selectSGNode(QSGNode *node) +{ + const QAbstractItemModel *model = m_sgSelectionModel->model(); + Model::used(model); + + const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, + QVariant::fromValue( + node), + 1, + Qt::MatchExactly | Qt::MatchRecursive + | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_sgSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::qObjectSelected(QObject *object) +{ + if (auto item = qobject_cast(object)) + selectItem(item); + else if (auto window = qobject_cast(object)) + selectWindow(window); +} + +void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) +{ + auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); + if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) + selectSGNode(reinterpret_cast(object)); +} + +void QuickInspector::objectCreated(QObject *object) +{ + if (QQuickWindow *window = qobject_cast(object)) { + if (QQuickView *view = qobject_cast(object)) { + if (view->engine()) { + m_probe->discoverObject(view->engine()); + } + } else { + QQmlContext *context = QQmlEngine::contextForObject(window); + QQmlEngine *engine = context ? context->engine() : nullptr; + + if (!engine) { + // engine = qmlEngine(window->contentItem()->childItems().value(0)); + QQuickItem *contentItem = window->contentItem(); + if (contentItem) { + const auto children = contentItem->childItems(); + if (!children.isEmpty()) { + engine = qmlEngine(children.first()); + } + } + } + + if (engine) { + m_probe->discoverObject(engine); + } + } + } +} + +void QuickInspector::recreateOverlay() +{ + ProbeGuard guard; + if (m_overlay) + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + + m_overlay = AbstractScreenGrabber::get(m_window); + if (!m_overlay) + return; + + connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); + // the target application might have destroyed the overlay widget + // (e.g. because the parent of the overlay got destroyed). + // just recreate a new one in this case + connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? + // It is for the widget inspector, but for qt quick? + connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); + m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); + + m_remoteView->setGrabberReady(true); +} + +void QuickInspector::aboutToCleanSceneGraph() +{ + m_sgModel->setWindow(nullptr); + m_currentSgNode = nullptr; + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::sceneGraphCleanedUp() +{ + m_sgModel->setWindow(m_window); +} + +void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) +{ + if (!m_window) + return; + RemoteViewFrame frame; + frame.setImage(grabbedFrame.image, grabbedFrame.transform); + frame.setSceneRect(grabbedFrame.itemsGeometryRect); + frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); + if (m_overlay && m_overlay->settings().componentsTraces) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); + else if (!grabbedFrame.itemsGeometry.isEmpty()) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); + m_remoteView->sendFrame(frame); +} + +void QuickInspector::slotGrabWindow() +{ + if (!m_remoteView->isActive() || !m_window) + return; + + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + if (m_overlay) { + m_overlay->requestGrabWindow(m_remoteView->userViewport()); + } +} + +void QuickInspector::setCustomRenderMode( + GammaRay::QuickInspectorInterface::RenderMode customRenderMode) +{ + + m_renderMode = customRenderMode; + + m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); + + const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; + if (m_overlay && m_overlay->settings().componentsTraces != tracing) { + auto settings = m_overlay->settings(); + settings.componentsTraces = tracing; + setOverlaySettings(settings); + } +} + +void QuickInspector::checkFeatures() +{ + Features f; + if (!m_window) { + emit features(f); + return; + } + + if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) + f = AllCustomRenderModes; + else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) + f = AnalyzePainting; + + emit features(f); +} + +void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) +{ + if (!m_overlay) { + emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. + return; + } + + m_overlay->setSettings(settings); + emit overlaySettings(m_overlay->settings()); +} + +void QuickInspector::checkOverlaySettings() +{ + emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); +} + +class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer +{ +public: + using QSGAbstractSoftwareRenderer::buildRenderList; + using QSGAbstractSoftwareRenderer::optimizeRenderList; + using QSGAbstractSoftwareRenderer::renderNodes; +}; + +#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) +// keep it working in UBSAN +__attribute__((no_sanitize("vptr"))) +#endif +void QuickInspector::analyzePainting() +{ + if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) + return; + + m_paintAnalyzer->beginAnalyzePainting(); + m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); + { + auto w = QQuickWindowPrivate::get(m_window); + auto renderer = static_cast(w->renderer); + + // this replicates what QSGSoftwareRender is doing + QPainter painter(m_paintAnalyzer->paintDevice()); + painter.setRenderHint(QPainter::Antialiasing); + auto rc = static_cast(w->renderer->context()); + auto prevPainter = rc->m_activePainter; + rc->m_activePainter = &painter; + renderer->markDirty(); + renderer->buildRenderList(); + renderer->optimizeRenderList(); + renderer->renderNodes(&painter); + + rc->m_activePainter = prevPainter; + } + m_paintAnalyzer->endAnalyzePainting(); +} + +void QuickInspector::checkSlowMode() +{ + // We can't check that for now as there is no getter for the property... + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::setSlowMode(bool slow) +{ + if (m_slowDownEnabled == slow) + return; + + static QHash connections; + + m_slowDownEnabled = slow; + + for (int i = 0; i < m_windowModel->rowCount(); ++i) { + const QModelIndex index = m_windowModel->index(i, 0); + QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); + auto it = connections.find(window); + + if (it == connections.end()) { + connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { + auto it = connections.find(window); +#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) + QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); +#else + QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); +#endif + QObject::disconnect(it.value()); + connections.erase(it); }, Qt::DirectConnection)); + } + } + + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::itemSelectionChanged(const QItemSelection &selection) +{ + const QModelIndex index = selection.value(0).topLeft(); + m_currentItem = index.data(ObjectModel::ObjectRole).value(); + m_itemPropertyController->setObject(m_currentItem); + + // It might be that a sg-node is already selected that belongs to this item, but isn't the root + // node of the Item. In this case we don't want to overwrite that selection. + if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { + m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); + const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); + auto proxy = qobject_cast(m_sgSelectionModel->model()); + m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); + } + + if (m_overlay) + m_overlay->placeOn(m_currentItem.data()); +} + +void QuickInspector::sgSelectionChanged(const QItemSelection &selection) +{ + if (selection.isEmpty()) + return; + + const QModelIndex index = selection.first().topLeft(); + m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); + if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) + return; // Apparently the node has been deleted meanwhile, so don't access it. + + void *obj = m_currentSgNode; + auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); + m_sgPropertyController->setObject(m_currentSgNode, mo->className()); + + m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); + selectItem(m_currentItem); +} + +void QuickInspector::sgNodeDeleted(QSGNode *node) +{ + if (m_currentSgNode == node) + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) +{ + if (!m_window) + return; + + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); + + if (!objects.isEmpty()) { + emit elementsAtReceived(objects, bestCandidate); + } +} + +void QuickInspector::pickElementId(const GammaRay::ObjectId &id) +{ + QQuickItem *item = id.asQObjectType(); + if (item) + m_probe->selectObject(item); +} + +QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const +{ + auto rect = parent->childrenRect(); + + const auto childItems = parent->childItems(); + for (const auto child : childItems) { + auto childRect = child->childrenRect(); + + // Get Global positon of childRect + QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); + + // Convert global position to local coordinates of the parent object + QPointF localChildPos = parent->mapFromScene(childGlobalPos); + + // Adjust childRect to be in local coordinates of the parent object + childRect.moveTopLeft(localChildPos.toPoint()); + + // Adding the childRect to the rect + rect = rect.united(childRect); + } + + return rect; +} + +ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, + GammaRay::RemoteViewInterface::RequestMode mode, + int &bestCandidate, bool parentIsGoodCandidate) const +{ + Q_ASSERT(parent); + ObjectIds objects; + + bestCandidate = -1; + if (parentIsGoodCandidate) { + // inherit the parent item opacity when looking for a good candidate item + // i.e. QQuickItem::isVisible is taking the parent into account already, but + // the opacity doesn't - we have to do this manually + // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem + // at least seems to not have this flag set. + parentIsGoodCandidate = isGoodCandidateItem(parent, true); + } + + auto childItems = parent->childItems(); + std::stable_sort(childItems.begin(), childItems.end(), + [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); + + for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order + const auto child = childItems.at(i); + const auto requestedPoint = parent->mapToItem(child, pos); + if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { + const int count = objects.size(); + int bc; // possibly better candidate among subChildren + objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); + + if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { + bestCandidate = count + bc; + } + } + + if (child->contains(requestedPoint)) { + if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { + bestCandidate = objects.size(); + } + objects << ObjectId(child); + } + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + break; + } + } + + if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { + bestCandidate = objects.size(); + } + + objects << ObjectId(parent); + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + objects = ObjectIds() << objects[bestCandidate]; + bestCandidate = 0; + } + + return objects; +} + + +void QuickInspector::scanForProblems() +{ + const QVector &allObjects = Probe::instance()->allQObjects(); + + QMutexLocker lock(Probe::objectLock()); + for (QObject *obj : allObjects) { + QQuickItem *item; + if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) + continue; + + QQuickItem *ancestor = item->parentItem(); + auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); + + while (ancestor && item->window() && ancestor != item->window()->contentItem()) { + if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { + auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); + + if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { + Problem p; + p.severity = Problem::Info; + p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); + p.object = ObjectId(item); + p.locations.push_back(ObjectDataProvider::creationLocation(item)); + p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); + p.findingCategory = Problem::Scan; + ProblemCollector::addProblem(p); + break; + } + } + ancestor = ancestor->parentItem(); + } + } +} + +bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEv = static_cast(event); + if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + QQuickWindow *window = qobject_cast(receiver); + if (window && window->contentItem()) { + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), + RemoteViewInterface::RequestBest, bestCandidate); + m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); + } + } + } + + return QObject::eventFilter(receiver, event); +} + +void QuickInspector::registerMetaTypes() +{ + MetaObject *mo = nullptr; + MO_ADD_METAOBJECT1(QQuickWindow, QWindow); + + MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); + MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); + MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); + +#ifndef QT_NO_OPENGL + MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); +#endif + + MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); + MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); + MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); + + MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); + + MO_ADD_METAOBJECT0(QSGRendererInterface); + MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); + + MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); + MO_ADD_PROPERTY_RO(QQuickView, engine); + MO_ADD_PROPERTY_RO(QQuickView, errors); + MO_ADD_PROPERTY_RO(QQuickView, initialSize); + MO_ADD_PROPERTY_RO(QQuickView, rootContext); + MO_ADD_PROPERTY_RO(QQuickView, rootObject); + + MO_ADD_METAOBJECT1(QQuickItem, QObject); + MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); + MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); + MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); + MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); + MO_ADD_PROPERTY(QQuickItem, flags, setFlags); + MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); + MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); + MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); + MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); + MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); + MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); + MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); + MO_ADD_PROPERTY_RO(QQuickItem, window); + + MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); + MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); + MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); + MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); + MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); + + MO_ADD_METAOBJECT1(QSGTexture, QObject); + MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); + MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); + MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); + MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); + MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); + // crashes without a current GL context + // MO_ADD_PROPERTY_RO(QSGTexture, textureId); + MO_ADD_PROPERTY_RO(QSGTexture, textureSize); + MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); + + MO_ADD_METAOBJECT0(QSGNode); + MO_ADD_PROPERTY_RO(QSGNode, parent); + MO_ADD_PROPERTY_RO(QSGNode, childCount); + MO_ADD_PROPERTY_RO(QSGNode, flags); + MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT +#endif + + MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); + MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); + + MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); + MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); + MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); + + MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); + MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); + + MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); + MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); + MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); + + MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); + + MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); + MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); + MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); + + MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); + MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); + MO_ADD_PROPERTY_RO(QSGRenderNode, flags); + MO_ADD_PROPERTY_RO(QSGRenderNode, rect); + MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); + MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); + MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); + + MO_ADD_METAOBJECT0(QSGMaterial); + MO_ADD_PROPERTY_RO(QSGMaterial, flags); + + MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); + + MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); + MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); + + MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); + +#ifndef QT_NO_OPENGL + MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); + + MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); + + MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); +#endif +} + +#define E(x) \ + { \ + QSGRendererInterface::x, #x \ + } +static const MetaEnum::Value qsg_graphics_api_table[] = { + E(Unknown), + E(Software), + E(OpenGL), + E(OpenVG), + E(Direct3D11), + E(Vulkan), + E(Metal), +}; + +static const MetaEnum::Value qsg_shader_compilation_type_table[] = { + E(RuntimeCompilation), + E(OfflineCompilation) +}; + +static const MetaEnum::Value qsg_shader_source_type_table[] = { + E(ShaderSourceString), + E(ShaderSourceFile), + E(ShaderByteCode) +}; + +static const MetaEnum::Value qsg_shader_type_table[] = { + E(UnknownShadingLanguage), + E(GLSL), + E(HLSL) +}; +#undef E + +#define E(x) \ + { \ + QSGRenderNode::x, #x \ + } +static const MetaEnum::Value render_node_state_flags_table[] = { + E(DepthState), + E(StencilState), + E(ScissorState), + E(ColorState), + E(BlendState), + E(CullState), + E(CullState), + E(ViewportState), + E(RenderTargetState) +}; + +static const MetaEnum::Value render_node_rendering_flags_table[] = { + E(BoundedRectRendering), + E(DepthAwareRendering), + E(OpaqueRendering) +}; +#undef E + +static QString anchorLineToString(const QQuickAnchorLine &line) +{ + if (!line.item + || line.anchorLine == QQuickAnchors::InvalidAnchor) { + return QStringLiteral(""); + } + QString s = Util::shortDisplayString(line.item); + switch (line.anchorLine) { + case QQuickAnchors::LeftAnchor: + return s + QStringLiteral(".left"); + case QQuickAnchors::RightAnchor: + return s + QStringLiteral(".right"); + case QQuickAnchors::TopAnchor: + return s + QStringLiteral(".top"); + case QQuickAnchors::BottomAnchor: + return s + QStringLiteral(".bottom"); + case QQuickAnchors::HCenterAnchor: + return s + QStringLiteral(".horizontalCenter"); + case QQuickAnchors::VCenterAnchor: + return s + QStringLiteral(".verticalCenter"); + case QQuickAnchors::BaselineAnchor: + return s + QStringLiteral(".baseline"); + default: + break; + } + return s; +} + +void QuickInspector::registerVariantHandlers() +{ + ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); + ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); + ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); + ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); + ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); + ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); + + VariantHandler::registerStringConverter( + qQuickPaintedItemPerformanceHintsToString); + VariantHandler::registerStringConverter(anchorLineToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(qsgMaterialFlagsToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); + + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); +} + +void QuickInspector::registerPCExtensions() +{ +#ifndef QT_NO_OPENGL + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + + PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); +#endif + PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); + PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); + + BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); +} diff --git a/.history/plugins/quickinspector/quickinspector_20260413093955.cpp b/.history/plugins/quickinspector/quickinspector_20260413093955.cpp new file mode 100644 index 0000000000..1c2dc6abd8 --- /dev/null +++ b/.history/plugins/quickinspector/quickinspector_20260413093955.cpp @@ -0,0 +1,1201 @@ +/* + quickinspector.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ + +#include "quickinspector.h" + +#include "quickanchorspropertyadaptor.h" +#include "quickitemmodel.h" +#include "quickscenegraphmodel.h" +#include "quickscreengrabber.h" +#include "quickpaintanalyzerextension.h" +#include "geometryextension/sggeometryextension.h" +#include "materialextension/materialextension.h" +#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" +#include "textureextension/qsgtexturegrabber.h" +#include "textureextension/textureextension.h" + +#include "quickimplicitbindingdependencyprovider.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#ifndef QT_NO_OPENGL +#include +#endif +#include +#include + +Q_DECLARE_METATYPE(QQmlError) + +Q_DECLARE_METATYPE(QQuickItem::Flags) +Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) +Q_DECLARE_METATYPE(QSGNode *) +Q_DECLARE_METATYPE(QSGBasicGeometryNode *) +Q_DECLARE_METATYPE(QSGGeometryNode *) +Q_DECLARE_METATYPE(QSGClipNode *) +Q_DECLARE_METATYPE(QSGTransformNode *) +Q_DECLARE_METATYPE(QSGRootNode *) +Q_DECLARE_METATYPE(QSGOpacityNode *) +Q_DECLARE_METATYPE(QSGNode::Flags) +Q_DECLARE_METATYPE(QSGNode::DirtyState) +Q_DECLARE_METATYPE(QSGGeometry *) +Q_DECLARE_METATYPE(QMatrix4x4 *) +Q_DECLARE_METATYPE(const QMatrix4x4 *) +Q_DECLARE_METATYPE(const QSGClipNode *) +Q_DECLARE_METATYPE(const QSGGeometry *) +Q_DECLARE_METATYPE(QSGMaterial *) +Q_DECLARE_METATYPE(QSGMaterial::Flags) +Q_DECLARE_METATYPE(QSGTexture::WrapMode) +Q_DECLARE_METATYPE(QSGTexture::Filtering) +Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) +Q_DECLARE_METATYPE(QSGRenderNode *) +Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) +Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) +Q_DECLARE_METATYPE(QSGRendererInterface *) +Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) +Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) + +using namespace GammaRay; + +#define E(x) \ + { \ + QQuickItem::x, #x \ + } +static const MetaEnum::Value qqitem_flag_table[] = { + E(ItemClipsChildrenToShape), + E(ItemAcceptsInputMethod), + E(ItemIsFocusScope), + E(ItemHasContents), + E(ItemAcceptsDrops) +}; +#undef E + +static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) +{ + QStringList list; + if (hints & QQuickPaintedItem::FastFBOResizing) + list << QStringLiteral("FastFBOResizing"); + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGNode::x, #x \ + } +static const MetaEnum::Value qsg_node_flag_table[] = { + E(OwnedByParent), + E(UsePreprocess), + E(OwnsGeometry), + E(OwnsMaterial), + E(OwnsOpaqueMaterial) +}; + +static const MetaEnum::Value qsg_node_dirtystate_table[] = { + E(DirtySubtreeBlocked), + E(DirtyMatrix), + E(DirtyNodeAdded), + E(DirtyNodeRemoved), + E(DirtyGeometry), + E(DirtyMaterial), + E(DirtyOpacity), + E(DirtyForceUpdate), + E(DirtyUsePreprocess), + E(DirtyPropagationMask) +}; +#undef E + +static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) +{ + QStringList list; +#define F(f) \ + if (flags & QSGMaterial::f) \ + list.push_back(QStringLiteral(#f)); + F(Blending) + F(RequiresDeterminant) + F(RequiresFullMatrixExceptTranslate) + F(RequiresFullMatrix) + F(NoBatching) +#undef F + + if (list.isEmpty()) + return QStringLiteral(""); + return list.join(QStringLiteral(" | ")); +} + +#define E(x) \ + { \ + QSGTexture::x, #x \ + } +static const MetaEnum::Value qsg_texture_anisotropy_table[] = { + E(AnisotropyNone), + E(Anisotropy2x), + E(Anisotropy4x), + E(Anisotropy8x), + E(Anisotropy16x) +}; + +static const MetaEnum::Value qsg_texture_filtering_table[] = { + E(None), + E(Nearest), + E(Linear) +}; + +static const MetaEnum::Value qsg_texture_wrapmode_table[] = { + E(Repeat), + E(ClampToEdge), + E(MirroredRepeat) +}; + +#undef E + +static bool itemHasContents(QQuickItem *item) +{ + return item->flags().testFlag(QQuickItem::ItemHasContents); +} + +static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) +{ + return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); +} + +static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) +{ + switch (customRenderMode) { + case QuickInspectorInterface::VisualizeClipping: + return QByteArray("clip"); + case QuickInspectorInterface::VisualizeOverdraw: + return QByteArray("overdraw"); + case QuickInspectorInterface::VisualizeBatches: + return QByteArray("batches"); + case QuickInspectorInterface::VisualizeChanges: + return QByteArray("changes"); + case QuickInspectorInterface::VisualizeTraces: + case QuickInspectorInterface::NormalRendering: + break; + } + return QByteArray(); +} + +QMutex RenderModeRequest::mutex; + +RenderModeRequest::RenderModeRequest(QObject *parent) + : QObject(parent) + , mode(QuickInspectorInterface::NormalRendering) +{ +} + +RenderModeRequest::~RenderModeRequest() +{ + QMutexLocker lock(&mutex); + + window.clear(); + + if (connection) + disconnect(connection); +} + +void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) +{ + if (toWindow) { + QMutexLocker lock(&mutex); + // Qt does some performance optimizations that break custom render modes. + // Thus the optimizations are only applied if there is no custom render mode set. + // So we need to make the scenegraph recheck whether a custom render mode is set. + // We do this by simply cleaning the scene graph which will recreate the renderer. + // We need however to do that at the proper time from the gui thread. + + if (!connection || (mode != customRenderMode || window != toWindow)) { + if (connection) + disconnect(connection); + mode = customRenderMode; + window = toWindow; + connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); + // trigger window update so afterRendering is emitted + QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); + } + } +} + +void RenderModeRequest::apply() +{ + QMutexLocker lock(&mutex); + + if (connection) + disconnect(connection); + + if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) + return; + + if (window) { + const QByteArray mode = renderModeToString(RenderModeRequest::mode); + QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); + QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { + emit aboutToCleanSceneGraph(); + QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); + winPriv->visualizationMode = mode; + emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); + } + + QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); +} + +void RenderModeRequest::preFinished() +{ + QMutexLocker lock(&mutex); + + if (window) + window->update(); + + emit finished(); +} + +QuickInspector::QuickInspector(Probe *probe, QObject *parent) + : QuickInspectorInterface(parent) + , m_probe(probe) + , m_currentSgNode(nullptr) + , m_itemModel(new QuickItemModel(this)) + , m_sgModel(new QuickSceneGraphModel(this)) + , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), + this)) + , m_sgPropertyController(new PropertyController(QStringLiteral( + "com.kdab.GammaRay.QuickSceneGraph"), + this)) + , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) + , m_pendingRenderMode(new RenderModeRequest(this)) + , m_renderMode(QuickInspectorInterface::NormalRendering) + , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) + , m_slowDownEnabled(false) +{ + registerMetaTypes(); + registerVariantHandlers(); + probe->installGlobalEventFilter(this); + + QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); + windowModel->setSourceModel(probe->objectListModel()); + QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); + proxy->setSourceModel(windowModel); + m_windowModel = proxy; + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); + + auto filterProxy = new ServerProxyModel(this); + filterProxy->setSourceModel(m_itemModel); + filterProxy->addRole(ObjectModel::ObjectIdRole); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); + + if (m_probe->needsObjectDiscovery()) { + connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); + } + + connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); + connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); + connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); + connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); + connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); + connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); + + m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); + connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::itemSelectionChanged); + + auto sgFilterProxy = new ServerProxyModel(this); + sgFilterProxy->setSourceModel(m_sgModel); + probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); + + m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); + connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, + this, &QuickInspector::sgSelectionChanged); + connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); + + connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); + connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); + connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); + connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); + connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); + connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); + +#ifndef QT_NO_OPENGL + auto texGrab = new QSGTextureGrabber(this); + connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); +#endif + + connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { + if (m_overlay) + m_overlay->placeOn(ItemOrLayoutFacade()); + }); + + ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", + "QtQuick Item check", + "Warns about items that are visible but out of view.", + &QuickInspector::scanForProblems); + + // needs to be last, extensions require some of the above to be set up correctly + registerPCExtensions(); +} + +QuickInspector::~QuickInspector() +{ + if (m_overlay) { + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + } +} + +void QuickInspector::selectWindow(int index) +{ + const QModelIndex mi = m_windowModel->index(index, 0); + QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); + selectWindow(window); +} + +void QuickInspector::selectWindow(QQuickWindow *window) +{ + if (m_window == window) { + return; + } + + if (m_window) { + const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; + + if (!mode.isEmpty()) { + auto reset = new RenderModeRequest(m_window); + connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); + reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); + } + } + + m_window = window; + m_itemModel->setWindow(window); + m_sgModel->setWindow(window); + m_remoteView->setEventReceiver(m_window); + m_remoteView->resetView(); + recreateOverlay(); + + if (m_window) { + // make sure we have selected something for the property editor to not be entirely empty + selectItem(m_window->contentItem()); + m_window->update(); + } + + checkFeatures(); + + if (m_window) + setCustomRenderMode(m_renderMode); +} + +void QuickInspector::selectItem(QQuickItem *item) +{ + const QAbstractItemModel *model = m_itemSelectionModel->model(); + Model::used(model); + Model::used(m_sgSelectionModel->model()); + + const QModelIndexList indexList = model->match(model->index(0, 0), + ObjectModel::ObjectRole, + QVariant::fromValue(item), 1, + Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_itemSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::selectSGNode(QSGNode *node) +{ + const QAbstractItemModel *model = m_sgSelectionModel->model(); + Model::used(model); + + const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, + QVariant::fromValue( + node), + 1, + Qt::MatchExactly | Qt::MatchRecursive + | Qt::MatchWrap); + if (indexList.isEmpty()) + return; + + const QModelIndex index = indexList.first(); + m_sgSelectionModel->select(index, + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); +} + +void QuickInspector::qObjectSelected(QObject *object) +{ + if (auto item = qobject_cast(object)) + selectItem(item); + else if (auto window = qobject_cast(object)) + selectWindow(window); +} + +void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) +{ + auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); + if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) + selectSGNode(reinterpret_cast(object)); +} + +void QuickInspector::objectCreated(QObject *object) +{ + if (QQuickWindow *window = qobject_cast(object)) { + if (QQuickView *view = qobject_cast(object)) { + if (view->engine()) { + m_probe->discoverObject(view->engine()); + } + } else { + QQmlContext *context = QQmlEngine::contextForObject(window); + QQmlEngine *engine = context ? context->engine() : nullptr; + + if (!engine) { + // engine = qmlEngine(window->contentItem()->childItems().value(0)); + QQuickItem *contentItem = window->contentItem(); + if (contentItem) { + const auto children = contentItem->childItems(); + if (!children.isEmpty()) { + engine = qmlEngine(children.first()); + } + } + } + + if (engine) { + m_probe->discoverObject(engine); + } + } + } +} + +void QuickInspector::recreateOverlay() +{ + ProbeGuard guard; + if (m_overlay) + disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); + + m_overlay = AbstractScreenGrabber::get(m_window); + if (!m_overlay) + return; + + connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); + connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); + // the target application might have destroyed the overlay widget + // (e.g. because the parent of the overlay got destroyed). + // just recreate a new one in this case + connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? + // It is for the widget inspector, but for qt quick? + connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); + m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); + + m_remoteView->setGrabberReady(true); +} + +void QuickInspector::aboutToCleanSceneGraph() +{ + m_sgModel->setWindow(nullptr); + m_currentSgNode = nullptr; + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::sceneGraphCleanedUp() +{ + m_sgModel->setWindow(m_window); +} + +void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) +{ + if (!m_window) + return; + RemoteViewFrame frame; + frame.setImage(grabbedFrame.image, grabbedFrame.transform); + frame.setSceneRect(grabbedFrame.itemsGeometryRect); + frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); + if (m_overlay && m_overlay->settings().componentsTraces) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); + else if (!grabbedFrame.itemsGeometry.isEmpty()) + frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); + m_remoteView->sendFrame(frame); +} + +void QuickInspector::slotGrabWindow() +{ + if (!m_remoteView->isActive() || !m_window) + return; + + Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); + if (m_overlay) { + m_overlay->requestGrabWindow(m_remoteView->userViewport()); + } +} + +void QuickInspector::setCustomRenderMode( + GammaRay::QuickInspectorInterface::RenderMode customRenderMode) +{ + + m_renderMode = customRenderMode; + + m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); + + const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; + if (m_overlay && m_overlay->settings().componentsTraces != tracing) { + auto settings = m_overlay->settings(); + settings.componentsTraces = tracing; + setOverlaySettings(settings); + } +} + +void QuickInspector::checkFeatures() +{ + Features f; + if (!m_window) { + emit features(f); + return; + } + + if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) + f = AllCustomRenderModes; + else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) + f = AnalyzePainting; + + emit features(f); +} + +void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) +{ + if (!m_overlay) { + emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. + return; + } + + m_overlay->setSettings(settings); + emit overlaySettings(m_overlay->settings()); +} + +void QuickInspector::checkOverlaySettings() +{ + emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); +} + +class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer +{ +public: + using QSGAbstractSoftwareRenderer::buildRenderList; + using QSGAbstractSoftwareRenderer::optimizeRenderList; + using QSGAbstractSoftwareRenderer::renderNodes; +}; + +#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) +// keep it working in UBSAN +__attribute__((no_sanitize("vptr"))) +#endif +void QuickInspector::analyzePainting() +{ + if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) + return; + + m_paintAnalyzer->beginAnalyzePainting(); + m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); + { + auto w = QQuickWindowPrivate::get(m_window); + auto renderer = static_cast(w->renderer); + + // this replicates what QSGSoftwareRender is doing + QPainter painter(m_paintAnalyzer->paintDevice()); + painter.setRenderHint(QPainter::Antialiasing); + auto rc = static_cast(w->renderer->context()); + auto prevPainter = rc->m_activePainter; + rc->m_activePainter = &painter; + renderer->markDirty(); + renderer->buildRenderList(); + renderer->optimizeRenderList(); + renderer->renderNodes(&painter); + + rc->m_activePainter = prevPainter; + } + m_paintAnalyzer->endAnalyzePainting(); +} + +void QuickInspector::checkSlowMode() +{ + // We can't check that for now as there is no getter for the property... + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::setSlowMode(bool slow) +{ + if (m_slowDownEnabled == slow) + return; + + static QHash connections; + + m_slowDownEnabled = slow; + + for (int i = 0; i < m_windowModel->rowCount(); ++i) { + const QModelIndex index = m_windowModel->index(i, 0); + QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); + auto it = connections.find(window); + + if (it == connections.end()) { + connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { + auto it = connections.find(window); +#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) + QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); +#else + QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); +#endif + QObject::disconnect(it.value()); + connections.erase(it); }, Qt::DirectConnection)); + } + } + + emit slowModeChanged(m_slowDownEnabled); +} + +void QuickInspector::itemSelectionChanged(const QItemSelection &selection) +{ + const QModelIndex index = selection.value(0).topLeft(); + m_currentItem = index.data(ObjectModel::ObjectRole).value(); + m_itemPropertyController->setObject(m_currentItem); + + // It might be that a sg-node is already selected that belongs to this item, but isn't the root + // node of the Item. In this case we don't want to overwrite that selection. + if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { + m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); + const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); + auto proxy = qobject_cast(m_sgSelectionModel->model()); + m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), + QItemSelectionModel::Select + | QItemSelectionModel::Clear + | QItemSelectionModel::Rows + | QItemSelectionModel::Current); + } + + if (m_overlay) + m_overlay->placeOn(m_currentItem.data()); +} + +void QuickInspector::sgSelectionChanged(const QItemSelection &selection) +{ + if (selection.isEmpty()) + return; + + const QModelIndex index = selection.first().topLeft(); + m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); + if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) + return; // Apparently the node has been deleted meanwhile, so don't access it. + + void *obj = m_currentSgNode; + auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); + m_sgPropertyController->setObject(m_currentSgNode, mo->className()); + + m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); + selectItem(m_currentItem); +} + +void QuickInspector::sgNodeDeleted(QSGNode *node) +{ + if (m_currentSgNode == node) + m_sgPropertyController->setObject(nullptr, QString()); +} + +void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) +{ + if (!m_window) + return; + + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); + + if (!objects.isEmpty()) { + emit elementsAtReceived(objects, bestCandidate); + } +} + +void QuickInspector::pickElementId(const GammaRay::ObjectId &id) +{ + QQuickItem *item = id.asQObjectType(); + if (item) + m_probe->selectObject(item); +} + +QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const +{ + auto rect = parent->childrenRect(); + + const auto childItems = parent->childItems(); + for (const auto child : childItems) { + auto childRect = child->childrenRect(); + + // Get Global positon of childRect + QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); + + // Convert global position to local coordinates of the parent object + QPointF localChildPos = parent->mapFromScene(childGlobalPos); + + // Adjust childRect to be in local coordinates of the parent object + childRect.moveTopLeft(localChildPos.toPoint()); + + // Adding the childRect to the rect + rect = rect.united(childRect); + } + + return rect; +} + +ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, + GammaRay::RemoteViewInterface::RequestMode mode, + int &bestCandidate, bool parentIsGoodCandidate) const +{ + Q_ASSERT(parent); + ObjectIds objects; + + bestCandidate = -1; + if (parentIsGoodCandidate) { + // inherit the parent item opacity when looking for a good candidate item + // i.e. QQuickItem::isVisible is taking the parent into account already, but + // the opacity doesn't - we have to do this manually + // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem + // at least seems to not have this flag set. + parentIsGoodCandidate = isGoodCandidateItem(parent, true); + } + + auto childItems = parent->childItems(); + std::stable_sort(childItems.begin(), childItems.end(), + [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); + + for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order + const auto child = childItems.at(i); + const auto requestedPoint = parent->mapToItem(child, pos); + if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { + const int count = objects.size(); + int bc; // possibly better candidate among subChildren + objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); + + if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { + bestCandidate = count + bc; + } + } + + if (child->contains(requestedPoint)) { + if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { + bestCandidate = objects.size(); + } + objects << ObjectId(child); + } + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + break; + } + } + + if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { + bestCandidate = objects.size(); + } + + objects << ObjectId(parent); + + if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { + objects = ObjectIds() << objects[bestCandidate]; + bestCandidate = 0; + } + + return objects; +} + + +void QuickInspector::scanForProblems() +{ + const QVector &allObjects = Probe::instance()->allQObjects(); + + QMutexLocker lock(Probe::objectLock()); + for (QObject *obj : allObjects) { + QQuickItem *item; + if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) + continue; + + QQuickItem *ancestor = item->parentItem(); + auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); + + while (ancestor && item->window() && ancestor != item->window()->contentItem()) { + if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { + auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); + + if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { + Problem p; + p.severity = Problem::Info; + p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); + p.object = ObjectId(item); + p.locations.push_back(ObjectDataProvider::creationLocation(item)); + p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); + p.findingCategory = Problem::Scan; + ProblemCollector::addProblem(p); + break; + } + } + ancestor = ancestor->parentItem(); + } + } +} + +bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEv = static_cast(event); + if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + QQuickWindow *window = qobject_cast(receiver); + if (window && window->contentItem()) { + int bestCandidate; + const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), + RemoteViewInterface::RequestBest, bestCandidate); + m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); + } + } + } + + return QObject::eventFilter(receiver, event); +} + +void QuickInspector::registerMetaTypes() +{ + MetaObject *mo = nullptr; + MO_ADD_METAOBJECT1(QQuickWindow, QWindow); + + MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); + MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); + MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); + +#ifndef QT_NO_OPENGL + MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); +#endif + + MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); + MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); + MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); + + MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); + + MO_ADD_METAOBJECT0(QSGRendererInterface); + MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); + MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); + + MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); + MO_ADD_PROPERTY_RO(QQuickView, engine); + MO_ADD_PROPERTY_RO(QQuickView, errors); + MO_ADD_PROPERTY_RO(QQuickView, initialSize); + MO_ADD_PROPERTY_RO(QQuickView, rootContext); + MO_ADD_PROPERTY_RO(QQuickView, rootObject); + + MO_ADD_METAOBJECT1(QQuickItem, QObject); + MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); + MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); + MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); + MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); + MO_ADD_PROPERTY(QQuickItem, flags, setFlags); + MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); + MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); + MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); + MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); + MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); + MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); + MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); + MO_ADD_PROPERTY_RO(QQuickItem, window); + + MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); + MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); + MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); + MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); + MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); + + MO_ADD_METAOBJECT1(QSGTexture, QObject); + MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); + MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); + MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); + MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); + MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); + // crashes without a current GL context + // MO_ADD_PROPERTY_RO(QSGTexture, textureId); + MO_ADD_PROPERTY_RO(QSGTexture, textureSize); + MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); + + MO_ADD_METAOBJECT0(QSGNode); + MO_ADD_PROPERTY_RO(QSGNode, parent); + MO_ADD_PROPERTY_RO(QSGNode, childCount); + MO_ADD_PROPERTY_RO(QSGNode, flags); + MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); +#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) + MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT +#endif + + MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); + MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); + MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); + + MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); + MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); + MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); + MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); + + MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); + MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); + MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); + + MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); + MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); + MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); + + MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); + + MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); + MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); + MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); + + MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); + MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); + MO_ADD_PROPERTY_RO(QSGRenderNode, flags); + MO_ADD_PROPERTY_RO(QSGRenderNode, rect); + MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); + MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); + MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); + + MO_ADD_METAOBJECT0(QSGMaterial); + MO_ADD_PROPERTY_RO(QSGMaterial, flags); + + MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); + + MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); + MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); + MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); + + MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); + +#ifndef QT_NO_OPENGL + MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); + MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); + + MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); + + MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); + MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); +#endif +} + +#define E(x) \ + { \ + QSGRendererInterface::x, #x \ + } +static const MetaEnum::Value qsg_graphics_api_table[] = { + E(Unknown), + E(Software), + E(OpenGL), + E(OpenVG), + E(Direct3D11), + E(Vulkan), + E(Metal), +}; + +static const MetaEnum::Value qsg_shader_compilation_type_table[] = { + E(RuntimeCompilation), + E(OfflineCompilation) +}; + +static const MetaEnum::Value qsg_shader_source_type_table[] = { + E(ShaderSourceString), + E(ShaderSourceFile), + E(ShaderByteCode) +}; + +static const MetaEnum::Value qsg_shader_type_table[] = { + E(UnknownShadingLanguage), + E(GLSL), + E(HLSL) +}; +#undef E + +#define E(x) \ + { \ + QSGRenderNode::x, #x \ + } +static const MetaEnum::Value render_node_state_flags_table[] = { + E(DepthState), + E(StencilState), + E(ScissorState), + E(ColorState), + E(BlendState), + E(CullState), + E(CullState), + E(ViewportState), + E(RenderTargetState) +}; + +static const MetaEnum::Value render_node_rendering_flags_table[] = { + E(BoundedRectRendering), + E(DepthAwareRendering), + E(OpaqueRendering) +}; +#undef E + +static QString anchorLineToString(const QQuickAnchorLine &line) +{ + if (!line.item + || line.anchorLine == QQuickAnchors::InvalidAnchor) { + return QStringLiteral(""); + } + QString s = Util::shortDisplayString(line.item); + switch (line.anchorLine) { + case QQuickAnchors::LeftAnchor: + return s + QStringLiteral(".left"); + case QQuickAnchors::RightAnchor: + return s + QStringLiteral(".right"); + case QQuickAnchors::TopAnchor: + return s + QStringLiteral(".top"); + case QQuickAnchors::BottomAnchor: + return s + QStringLiteral(".bottom"); + case QQuickAnchors::HCenterAnchor: + return s + QStringLiteral(".horizontalCenter"); + case QQuickAnchors::VCenterAnchor: + return s + QStringLiteral(".verticalCenter"); + case QQuickAnchors::BaselineAnchor: + return s + QStringLiteral(".baseline"); + default: + break; + } + return s; +} + +void QuickInspector::registerVariantHandlers() +{ + ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); + ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); + ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); + ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); + ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); + ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); + + VariantHandler::registerStringConverter( + qQuickPaintedItemPerformanceHintsToString); + VariantHandler::registerStringConverter(anchorLineToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(qsgMaterialFlagsToString); + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); + + VariantHandler::registerStringConverter(Util::addressToString); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); + VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); + VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); +} + +void QuickInspector::registerPCExtensions() +{ +#ifndef QT_NO_OPENGL + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + PropertyController::registerExtension(); + + PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); +#endif + PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); + PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); + + BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); +} diff --git a/.history/probe/hooks_20260413092956.cpp b/.history/probe/hooks_20260413092956.cpp new file mode 100644 index 0000000000..09ed9d5f22 --- /dev/null +++ b/.history/probe/hooks_20260413092956.cpp @@ -0,0 +1,143 @@ +/* + hooks.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2012 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ +// krazy:excludeall=cpp due to low-level stuff in here + +#include + +#include "hooks.h" +#include "probecreator.h" + +#include + +#include + +#include + +#include //cannot use cstdio on QNX6.6 +#include + +#ifdef Q_OS_MAC +#include +#include +#include +#include +#elif defined(Q_OS_WIN) +#include +#endif + +#define IF_DEBUG(x) + +using namespace GammaRay; + +static void log_injection(const char *msg) +{ +#ifdef Q_OS_WIN + OutputDebugStringA(msg); +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" + printf(msg); // NOLINT clang-tidy +#pragma GCC diagnostic pop +#endif +} + +static void gammaray_pre_routine() +{ +#ifdef Q_OS_WIN + if (qApp) // DllMain will do a better job at this, we are too early here and might not even have our staticMetaObject properly resolved + return; +#endif + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) +Q_COREAPP_STARTUP_FUNCTION(gammaray_pre_routine) + +// previously installed Qt hooks, for daisy-chaining +static void (*gammaray_next_startup_hook)() = nullptr; +static void (*gammaray_next_addObject)(QObject *) = nullptr; +static void (*gammaray_next_removeObject)(QObject *) = nullptr; + +extern "C" Q_DECL_EXPORT void gammaray_startup_hook() +{ + Probe::startupHookReceived(); + new ProbeCreator(ProbeCreator::Create); + + if (gammaray_next_startup_hook) + gammaray_next_startup_hook(); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_addObject(QObject *obj) +{ + Probe::objectAdded(obj, true); + + if (gammaray_next_addObject) + gammaray_next_addObject(obj); +} + +extern "C" Q_DECL_EXPORT void gammaray_removeObject(QObject *obj) +{ + Probe::objectRemoved(obj); + + if (gammaray_next_removeObject) + gammaray_next_removeObject(obj); +} + +static void installQHooks() +{ + Q_ASSERT(qtHookData[QHooks::HookDataVersion] >= 1); + Q_ASSERT(qtHookData[QHooks::HookDataSize] >= 6); + + gammaray_next_addObject = reinterpret_cast(qtHookData[QHooks::AddQObject]); + gammaray_next_removeObject = reinterpret_cast(qtHookData[QHooks::RemoveQObject]); + gammaray_next_startup_hook = reinterpret_cast(qtHookData[QHooks::Startup]); + + qtHookData[QHooks::AddQObject] = reinterpret_cast(&gammaray_addObject); + qtHookData[QHooks::RemoveQObject] = reinterpret_cast(&gammaray_removeObject); + qtHookData[QHooks::Startup] = reinterpret_cast(&gammaray_startup_hook); +} + +bool Hooks::hooksInstalled() +{ + return qtHookData[QHooks::AddQObject] == reinterpret_cast(&gammaray_addObject); +} + +void Hooks::installHooks() +{ + if (hooksInstalled()) + return; + + installQHooks(); +} + +extern "C" Q_DECL_EXPORT void gammaray_probe_inject() +{ + if (!qApp) { + return; + } + Hooks::installHooks(); + log_injection("gammaray_probe_inject()\n"); + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_probe_attach() +{ + if (!qApp) { + return; + } + log_injection("gammaray_probe_attach()\n"); + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects | ProbeCreator::ResendServerAddress); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_install_hooks() +{ + Hooks::installHooks(); +} diff --git a/.history/probe/hooks_20260413093443.cpp b/.history/probe/hooks_20260413093443.cpp new file mode 100644 index 0000000000..9ee83e19c0 --- /dev/null +++ b/.history/probe/hooks_20260413093443.cpp @@ -0,0 +1,147 @@ +/* + hooks.cpp + + This file is part of GammaRay, the Qt application inspection and manipulation tool. + + SPDX-FileCopyrightText: 2012 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Volker Krause + + SPDX-License-Identifier: GPL-2.0-or-later + + Contact KDAB at for commercial licensing options. +*/ +// krazy:excludeall=cpp due to low-level stuff in here + +#include + +#include "hooks.h" +#include "probecreator.h" + +#include + +#include + +#include + +#include //cannot use cstdio on QNX6.6 +#include + +#ifdef Q_OS_MAC +#include +#include +#include +#include +#elif defined(Q_OS_WIN) +#include +#endif + +#define IF_DEBUG(x) + +using namespace GammaRay; + +static void log_injection(const char *msg) +{ +#ifdef Q_OS_WIN + OutputDebugStringA(msg); +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" + printf(msg); // NOLINT clang-tidy +#pragma GCC diagnostic pop +#endif +} + +static void gammaray_pre_routine() +{ +#ifdef Q_OS_WIN + if (qApp) // DllMain will do a better job at this, we are too early here and might not even have our staticMetaObject properly resolved + return; +#endif + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) +Q_COREAPP_STARTUP_FUNCTION(gammaray_pre_routine) + +// previously installed Qt hooks, for daisy-chaining +static void (*gammaray_next_startup_hook)() = nullptr; +static void (*gammaray_next_addObject)(QObject *) = nullptr; +static void (*gammaray_next_removeObject)(QObject *) = nullptr; + +extern "C" Q_DECL_EXPORT void gammaray_startup_hook() +{ + Probe::startupHookReceived(); + new ProbeCreator(ProbeCreator::Create); + + if (gammaray_next_startup_hook) + gammaray_next_startup_hook(); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_addObject(QObject *obj) +{ + Probe::objectAdded(obj, true); + + if (gammaray_next_addObject) + gammaray_next_addObject(obj); +} + +extern "C" Q_DECL_EXPORT void gammaray_removeObject(QObject *obj) +{ + Probe::objectRemoved(obj); + + if (gammaray_next_removeObject) + gammaray_next_removeObject(obj); +} + +static void installQHooks() +{ + Q_ASSERT(qtHookData[QHooks::HookDataVersion] >= 1); + Q_ASSERT(qtHookData[QHooks::HookDataSize] >= 6); + + gammaray_next_addObject = reinterpret_cast(qtHookData[QHooks::AddQObject]); + gammaray_next_removeObject = reinterpret_cast(qtHookData[QHooks::RemoveQObject]); + gammaray_next_startup_hook = reinterpret_cast(qtHookData[QHooks::Startup]); + + qtHookData[QHooks::AddQObject] = reinterpret_cast(&gammaray_addObject); + qtHookData[QHooks::RemoveQObject] = reinterpret_cast(&gammaray_removeObject); + qtHookData[QHooks::Startup] = reinterpret_cast(&gammaray_startup_hook); +} + +bool Hooks::hooksInstalled() +{ + return qtHookData[QHooks::AddQObject] == reinterpret_cast(&gammaray_addObject); +} + +void Hooks::installHooks() +{ + if (hooksInstalled()) + return; + + installQHooks(); +} + +extern "C" Q_DECL_EXPORT void gammaray_probe_inject() +{ + if (!qApp) { + return; + } + Hooks::installHooks(); + log_injection("gammaray_probe_inject()\n"); + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_probe_attach() +{ + if (!qApp) { + return; + } + + Hooks::installHooks(); + Probe::startupHookReceived(); + + log_injection("gammaray_probe_attach()\n"); + new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects | ProbeCreator::ResendServerAddress); +} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) + +extern "C" Q_DECL_EXPORT void gammaray_install_hooks() +{ + Hooks::installHooks(); +} diff --git a/core/probe.cpp b/core/probe.cpp index bb162e56be..85bdda8160 100644 --- a/core/probe.cpp +++ b/core/probe.cpp @@ -865,7 +865,15 @@ void Probe::discoverObject(QObject *object) return; objectAdded(object); - foreach (QObject *child, object->children()) { + + // foreach (QObject *child, object->children()) { + // discoverObject(child); + // } + + const auto children = object->children(); + for (QObject *child : children) { + if (!child) continue; + if (m_validObjects.contains(child)) continue; discoverObject(child); } } diff --git a/plugins/quickinspector/quickinspector.cpp b/plugins/quickinspector/quickinspector.cpp index 8a7a7da8be..1c2dc6abd8 100644 --- a/plugins/quickinspector/quickinspector.cpp +++ b/plugins/quickinspector/quickinspector.cpp @@ -512,16 +512,27 @@ void QuickInspector::objectCreated(QObject *object) { if (QQuickWindow *window = qobject_cast(object)) { if (QQuickView *view = qobject_cast(object)) { - m_probe->discoverObject(view->engine()); + if (view->engine()) { + m_probe->discoverObject(view->engine()); + } } else { QQmlContext *context = QQmlEngine::contextForObject(window); QQmlEngine *engine = context ? context->engine() : nullptr; if (!engine) { - engine = qmlEngine(window->contentItem()->childItems().value(0)); + // engine = qmlEngine(window->contentItem()->childItems().value(0)); + QQuickItem *contentItem = window->contentItem(); + if (contentItem) { + const auto children = contentItem->childItems(); + if (!children.isEmpty()) { + engine = qmlEngine(children.first()); + } + } + } + + if (engine) { + m_probe->discoverObject(engine); } - - m_probe->discoverObject(engine); } } } diff --git a/probe/hooks.cpp b/probe/hooks.cpp index 09ed9d5f22..9ee83e19c0 100644 --- a/probe/hooks.cpp +++ b/probe/hooks.cpp @@ -133,6 +133,10 @@ extern "C" Q_DECL_EXPORT void gammaray_probe_attach() if (!qApp) { return; } + + Hooks::installHooks(); + Probe::startupHookReceived(); + log_injection("gammaray_probe_attach()\n"); new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects | ProbeCreator::ResendServerAddress); } // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) From ec4c0b3ddc67967b300f7769e42aaa74e42dbb99 Mon Sep 17 00:00:00 2001 From: Kevin Ye <66344339+Kevinye0116@users.noreply.github.com> Date: Wed, 15 Apr 2026 16:07:24 +0800 Subject: [PATCH 2/3] Delete .history directory --- .history/core/probe_20260413092956.cpp | 999 -------------- .history/core/probe_20260413094137.cpp | 1007 -------------- .../quickinspector_20260413092956.cpp | 1190 ---------------- .../quickinspector_20260413093758.cpp | 1201 ----------------- .../quickinspector_20260413093955.cpp | 1201 ----------------- .history/probe/hooks_20260413092956.cpp | 143 -- .history/probe/hooks_20260413093443.cpp | 147 -- 7 files changed, 5888 deletions(-) delete mode 100644 .history/core/probe_20260413092956.cpp delete mode 100644 .history/core/probe_20260413094137.cpp delete mode 100644 .history/plugins/quickinspector/quickinspector_20260413092956.cpp delete mode 100644 .history/plugins/quickinspector/quickinspector_20260413093758.cpp delete mode 100644 .history/plugins/quickinspector/quickinspector_20260413093955.cpp delete mode 100644 .history/probe/hooks_20260413092956.cpp delete mode 100644 .history/probe/hooks_20260413093443.cpp diff --git a/.history/core/probe_20260413092956.cpp b/.history/core/probe_20260413092956.cpp deleted file mode 100644 index bb162e56be..0000000000 --- a/.history/core/probe_20260413092956.cpp +++ /dev/null @@ -1,999 +0,0 @@ -/* - probe.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ -// krazy:excludeall=null,captruefalse,staticobjects - -#include - -#include "probe.h" -#include "enumrepositoryserver.h" -#include "execution.h" -#include "classesiconsrepositoryserver.h" -#include "metaobjectrepository.h" -#include "objectlistmodel.h" -#include "objecttreemodel.h" -#include "probesettings.h" -#include "probecontroller.h" -#include "problemcollector.h" -#include "toolmanager.h" -#include "toolpluginmodel.h" -#include "util.h" -#include "varianthandler.h" -#include "metaobjectregistry.h" -#include "favoriteobject.h" - -#include "remote/server.h" -#include "remote/remotemodelserver.h" -#include "remote/serverproxymodel.h" -#include "remote/selectionmodelserver.h" -#include "toolpluginerrormodel.h" -#include "probeguard.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define IF_DEBUG(x) - -#ifdef ENABLE_EXPENSIVE_ASSERTS -#define EXPENSIVE_ASSERT(x) Q_ASSERT(x) -#else -#define EXPENSIVE_ASSERT(x) -#endif - -using namespace GammaRay; -using namespace std; - -QAtomicPointer Probe::s_instance = QAtomicPointer(nullptr); - -namespace GammaRay { -static void signal_begin_callback(QObject *caller, int method_index, void **argv) -{ - // Ignore event dispatcher signals - if (caller->inherits("QAbstractEventDispatcher")) - return; - - if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) - return; - - method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.signalBeginCallback) - callbacks.signalBeginCallback(caller, method_index, argv); - }); -} - -static void signal_end_callback(QObject *caller, int method_index) -{ - if (method_index == 0 || !Probe::instance()) - return; - - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()) - return; - if (!Probe::instance()->isValidObject(caller)) // implies filterObject() - return; // deleted in the slot - locker.unlock(); - - method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.signalEndCallback) - callbacks.signalEndCallback(caller, method_index); - }); -} - -static void slot_begin_callback(QObject *caller, int method_index, void **argv) -{ - if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) - return; - - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.slotBeginCallback) - callbacks.slotBeginCallback(caller, method_index, argv); - }); -} - -static void slot_end_callback(QObject *caller, int method_index) -{ - if (method_index == 0 || !Probe::instance()) - return; - - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(caller)) // implies filterObject() - return; // deleted in the slot - locker.unlock(); - - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.slotEndCallback) - callbacks.slotEndCallback(caller, method_index); - }); -} - -static QItemSelectionModel *selectionModelFactory(QAbstractItemModel *model) -{ - Q_ASSERT(!model->objectName().isEmpty()); - return new SelectionModelServer(model->objectName() + ".selection", model, Probe::instance()); -} -} - -// useful for debugging, dumps the object and all it's parents -// also usable from GDB! -void dumpObject(QObject *obj) -{ - if (!obj) { - cout << "QObject(0x0)" << endl; - return; - } - - const std::ios::fmtflags oldFlags(cout.flags()); - do { - cout << obj->metaObject()->className() << "(" << hex << obj << ")"; - obj = obj->parent(); - if (obj) - cout << " <- "; - } while (obj); - cout << endl; - cout.flags(oldFlags); -} - -struct Listener -{ - Listener() = default; - - bool trackDestroyed = true; - QVector addedBeforeProbeInstance; - - QHash constructionBacktracesForObjects; -}; - -Q_GLOBAL_STATIC(Listener, s_listener) - -// ensures proper information is returned by isValidObject by -// locking it in objectAdded/Removed -Q_GLOBAL_STATIC(QRecursiveMutex, s_lock) - -Probe::Probe(QObject *parent) - : QObject(parent) - , m_objectListModel(new ObjectListModel(this)) - , m_objectTreeModel(new ObjectTreeModel(this)) - , m_window(nullptr) - , m_metaObjectRegistry(new MetaObjectRegistry(this)) - , m_queueTimer(new QTimer(this)) - , m_server(nullptr) -{ - qputenv("DEBUGINFOD_URLS", QByteArray()); - - Q_ASSERT(thread() == qApp->thread()); - IF_DEBUG(cout << "attaching GammaRay probe" << endl;) - - StreamOperators::registerOperators(); - ProbeSettings::receiveSettings(); - - m_server = new Server(this); - - ObjectBroker::setSelectionModelFactoryCallback(selectionModelFactory); - ObjectBroker::registerObject(new ProbeController(this)); - m_toolManager = new ToolManager(this); - ObjectBroker::registerObject(m_toolManager); - ObjectBroker::registerObject(new FavoriteObject(this)); - - m_problemCollector = new ProblemCollector(this); - - ObjectBroker::registerObject(EnumRepositoryServer::create(this)); - ClassesIconsRepositoryServer::create(this); - registerModel(QStringLiteral("com.kdab.GammaRay.ObjectTree"), m_objectTreeModel); - registerModel(QStringLiteral("com.kdab.GammaRay.ObjectList"), m_objectListModel); - - ToolPluginModel *toolPluginModel = new ToolPluginModel( - m_toolManager->toolPluginManager()->plugins(), this); - registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginModel"), toolPluginModel); - ToolPluginErrorModel *toolPluginErrorModel = new ToolPluginErrorModel(m_toolManager->toolPluginManager()->errors(), this); - registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginErrorModel"), toolPluginErrorModel); - - m_queueTimer->setSingleShot(true); - m_queueTimer->setInterval(0); - connect(m_queueTimer, &QTimer::timeout, - this, &Probe::processQueuedObjectChanges); - - m_previousSignalSpyCallbackSet = qt_signal_spy_callback_set.loadRelaxed(); - - connect(this, &Probe::objectCreated, m_metaObjectRegistry, &MetaObjectRegistry::objectAdded); - connect(this, &Probe::objectDestroyed, m_metaObjectRegistry, &MetaObjectRegistry::objectRemoved); -} - -Probe::~Probe() -{ - emit aboutToDetach(); - IF_DEBUG(cerr << "detaching GammaRay probe" << endl;) - - // Remove hooks - qtHookData[QHooks::AddQObject] = 0; - qtHookData[QHooks::RemoveQObject] = 0; - qtHookData[QHooks::Startup] = 0; - - qt_register_signal_spy_callbacks(m_previousSignalSpyCallbackSet); - - ObjectBroker::clear(); - ProbeSettings::resetLauncherIdentifier(); - MetaObjectRepository::instance()->clear(); - VariantHandler::clear(); - - s_instance = QAtomicPointer(nullptr); -} - -void Probe::setWindow(QObject *window) -{ - m_window = window; -} - -QObject *Probe::window() const -{ - return m_window; -} - -MetaObjectRegistry *Probe::metaObjectRegistry() const -{ - return m_metaObjectRegistry; -} - -Probe *GammaRay::Probe::instance() -{ - return s_instance.loadRelaxed(); -} - -bool Probe::isInitialized() -{ - return instance(); -} - -bool Probe::canShowWidgets() -{ - return QCoreApplication::instance()->inherits("QApplication"); -} - -void Probe::createProbe(bool findExisting) -{ - Q_ASSERT(qApp); - Q_ASSERT(!isInitialized()); - - // first create the probe and its children - // we must not hold the object lock here as otherwise we can deadlock - // with other QObject's we create and other threads are using. One - // example are QAbstractSocketEngine. - IF_DEBUG(cout << "setting up new probe instance" << endl;) - Probe *probe = nullptr; - { - ProbeGuard guard; - probe = new Probe; - } - IF_DEBUG(cout << "done setting up new probe instance" << endl;) - - connect(qApp, &QCoreApplication::aboutToQuit, probe, &Probe::shutdown); - - // Our safety net, if there's no call to QCoreApplication::exec() we'll never receive the aboutToQuit() signal - // Make sure we still cleanup safely after the application instance got destroyed - connect(qApp, &QObject::destroyed, probe, &Probe::shutdown); - - // now we can get the lock and add items which where added before this point in time - { - QMutexLocker lock(s_lock()); - // now we set the instance while holding the lock, - // all future calls to object{Added,Removed} will - // act directly on the data structures there instead - // of using addedBeforeProbeInstance - // this will only happen _after_ the object lock above is released though - Q_ASSERT(!instance()); - - s_instance = QAtomicPointer(probe); - - // add objects to the probe that were tracked before its creation - foreach (QObject *obj, s_listener()->addedBeforeProbeInstance) { - objectAdded(obj); - } - s_listener()->addedBeforeProbeInstance.clear(); - - // try to find existing objects by other means - if (findExisting) - probe->findExistingObjects(); - } - - // eventually initialize the rest - QMetaObject::invokeMethod(probe, "delayedInit", Qt::QueuedConnection); -} - -void Probe::resendServerAddress() -{ - Q_ASSERT(isInitialized()); - Q_ASSERT(m_server); - if (!m_server->isListening()) // already connected - return; - ProbeSettings::receiveSettings(); - ProbeSettings::sendServerAddress(m_server->externalAddress()); -} - -void Probe::startupHookReceived() -{ -#ifdef Q_OS_ANDROID - QDir root = QDir::home(); - root.cdUp(); - Paths::setRootPath(root.absolutePath()); -#endif - s_listener()->trackDestroyed = false; -} - -void Probe::delayedInit() -{ - QCoreApplication::instance()->installEventFilter(this); - - QString appName = qApp->applicationName(); - if (appName.isEmpty() && !qApp->arguments().isEmpty()) { - appName = qApp->arguments().first().remove(qApp->applicationDirPath()); - if (appName.startsWith('.')) - appName = appName.right(appName.length() - 1); - if (appName.startsWith('/')) - appName = appName.right(appName.length() - 1); - } - if (appName.isEmpty()) - appName = tr("PID %1").arg(qApp->applicationPid()); - m_server->setLabel(appName); - // The applicationName might be translated, so let's go with the application file base name - m_server->setKey(QFileInfo(qApp->applicationFilePath()).completeBaseName()); - m_server->setPid(qApp->applicationPid()); - - if (ProbeSettings::value(QStringLiteral("RemoteAccessEnabled"), true).toBool()) { - const auto serverStarted = m_server->listen(); - if (serverStarted) { - ProbeSettings::sendServerAddress(m_server->externalAddress()); - } else { - ProbeSettings::sendServerLaunchError(m_server->errorString()); - } - } - - if (ProbeSettings::value(QStringLiteral("InProcessUi"), false).toBool()) - showInProcessUi(); -} - -void Probe::shutdown() -{ - delete this; -} - -void Probe::showInProcessUi() -{ - if (!canShowWidgets()) { - cerr << "Unable to show in-process UI in a non-QWidget based application." << endl; - return; - } - - IF_DEBUG(cout << "creating GammaRay::MainWindow" << endl;) - ProbeGuard guard; - - QLibrary lib; - for (auto &path : Paths::pluginPaths(QStringLiteral(GAMMARAY_PROBE_ABI))) { - path += QStringLiteral("/gammaray_inprocessui"); -#if defined(GAMMARAY_INSTALL_QT_LAYOUT) - path += '-'; - path += GAMMARAY_PROBE_ABI; -#else -#if !defined(Q_OS_MAC) -#if defined(QT_DEBUG) - path += QLatin1String(GAMMARAY_DEBUG_POSTFIX); -#endif -#endif -#endif - lib.setFileName(path); - if (lib.load()) - break; - } - - if (!lib.isLoaded()) { - std::cerr << "Failed to load in-process UI module: " - << qPrintable(lib.errorString()) - << std::endl; - } else { - void (*factory)() = reinterpret_cast(lib.resolve("gammaray_create_inprocess_mainwindow")); - if (!factory) - std::cerr << Q_FUNC_INFO << ' ' << qPrintable(lib.errorString()) << endl; - else - factory(); - } - - IF_DEBUG(cout << "creation done" << endl;) -} - -bool Probe::filterObject(QObject *obj) const -{ - QSet visitedObjects; - int iteration = 0; - QObject *o = obj; - do { - if (iteration > 100) { - // Probably we have a loop in the tree. Do loop detection. - if (visitedObjects.contains(o)) { - std::cerr << "We detected a loop in the object tree for object " << o; - if (!o->objectName().isEmpty()) - std::cerr << " \"" << qPrintable(o->objectName()) << "\""; - std::cerr << " (" << o->metaObject()->className() << ")." << std::endl; - return true; - } - visitedObjects << o; - } - ++iteration; - - if (o == this || o == window() || (qstrncmp(o->metaObject()->className(), "GammaRay::", 10) == 0)) { - return true; - } - o = o->parent(); - } while (o); - return false; -} - -void Probe::registerModel(const QString &objectName, QAbstractItemModel *model) -{ - auto *ms = new RemoteModelServer(objectName, model); - ms->setModel(model); - ObjectBroker::registerModelInternal(objectName, model); -} - -QAbstractItemModel *Probe::objectListModel() const -{ - return m_objectListModel; -} - -const QVector &Probe::allQObjects() const -{ - return m_objectListModel->objects(); -} - -QAbstractItemModel *Probe::objectTreeModel() const -{ - return m_objectTreeModel; -} - -ProblemCollector *Probe::problemCollector() const -{ - return m_problemCollector; -} - -QRecursiveMutex *Probe::objectLock() -{ - return s_lock(); -} - -/* - * We need to handle 4 different cases in here: - * (1) our thread, from ctor: - * - wait until next event-loop re-entry of our thread - * - emit objectCreated if object still valid - * (2) our thread, after ctor: - * - emit objectCreated right away - * (3) other thread, from ctor: - * - wait until next event-loop re-entry in other thread (FIXME: we do not currently do this!!) - * - post information to our thread - * (4) other thread, after ctor: - * - post information to our thread - * - emit objectCreated there right away if object still valid - * - * Pre-conditions: lock may or may not be held already, arbitrary thread - */ -void Probe::objectAdded(QObject *obj, bool fromCtor) -{ - if (obj == nullptr) - return; - QMutexLocker lock(s_lock()); - - // attempt to ignore objects created by GammaRay itself, especially short-lived ones - if (fromCtor && ProbeGuard::insideProbe() && obj->thread() == QThread::currentThread()) - return; - - // ignore objects created when global statics are already getting destroyed (on exit) - if (s_listener.isDestroyed()) - return; - - - if (Execution::hasFastStackTrace() && fromCtor) { - s_listener()->constructionBacktracesForObjects.insert(obj, Execution::stackTrace(32, 2)); // skip 2: this and the hook function calling us - } - - if (!isInitialized()) { - IF_DEBUG(cout - << "objectAdded Before: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - s_listener()->addedBeforeProbeInstance << obj; - return; - } - - if (instance()->filterObject(obj)) { - IF_DEBUG(cout - << "objectAdded Filter: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - return; - } - - if (instance()->m_validObjects.contains(obj)) { - // this happens when we get a child event before the objectAdded call from the ctor - // or when we add an item from addedBeforeProbeInstance who got added already - // due to the add-parent-before-child logic - IF_DEBUG(cout - << "objectAdded Known: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - return; - } - - // make sure we already know the parent - if (obj->parent() && !instance()->m_validObjects.contains(obj->parent())) - objectAdded(obj->parent(), fromCtor); - Q_ASSERT(!obj->parent() || instance()->m_validObjects.contains(obj->parent())); - - instance()->m_validObjects << obj; - - if (!fromCtor && obj->parent() && instance()->isObjectCreationQueued(obj->parent())) { - // when a child event triggers a call to objectAdded while inside the ctor - // the parent is already tracked but it's call to objectFullyConstructed - // was delayed. hence we must do the same for the child for integrity - fromCtor = true; - } - - IF_DEBUG(cout << "objectAdded: " << hex << obj - << (fromCtor ? " (from ctor)" : "") - << ", p: " << obj->parent() << endl;) - - if (fromCtor) - instance()->queueCreatedObject(obj); - else - instance()->objectFullyConstructed(obj); -} - -// pre-conditions: lock may or may not be held already, our thread -void Probe::processQueuedObjectChanges() -{ - QMutexLocker lock(s_lock()); - - IF_DEBUG(cout << Q_FUNC_INFO << " " << m_queuedObjectChanges.size() << endl;) - - // must be called from the main thread via timeout - Q_ASSERT(QThread::currentThread() == thread()); - - const auto queuedObjectChanges = m_queuedObjectChanges; // copy, in case this gets modified while we iterate (which can actually happen) - for (const auto &change : queuedObjectChanges) { - switch (change.type) { - case ObjectChange::Create: - objectFullyConstructed(change.obj); - break; - case ObjectChange::Destroy: - emit objectDestroyed(change.obj); - break; - } - } - - IF_DEBUG(cout << Q_FUNC_INFO << " done" << endl;) - - m_queuedObjectChanges.clear(); - - for (QObject *obj : std::as_const(m_pendingReparents)) { - if (!isValidObject(obj)) - continue; - if (filterObject(obj)) // the move might have put it under a hidden parent - objectRemoved(obj); - else - emit objectReparented(obj); - } - m_pendingReparents.clear(); -} - -// pre-condition: lock is held already, our thread -void Probe::objectFullyConstructed(QObject *obj) -{ - Q_ASSERT(thread() == QThread::currentThread()); - - if (!m_validObjects.contains(obj)) { - // deleted already - IF_DEBUG(cout << "stale fully constructed: " << hex << obj << endl;) - return; - } - - if (filterObject(obj)) { - // when the call was delayed from the ctor construction, - // the parent might not have been set properly yet. hence - // apply the filter again - m_validObjects.remove(obj); - IF_DEBUG(cout << "now filtered fully constructed: " << hex << obj << endl;) - return; - } - - IF_DEBUG(cout << "fully constructed: " << hex << obj << endl;) - - // ensure we know all our ancestors already - for (QObject *parent = obj->parent(); parent; parent = parent->parent()) { - if (!m_validObjects.contains(parent)) { - objectAdded(parent); // will also handle any further ancestors - break; - } - } - Q_ASSERT(!obj->parent() || m_validObjects.contains(obj->parent())); - - m_toolManager->objectAdded(obj); - emit objectCreated(obj); -} - -/* - * We have two cases to consider here: - * (1) our thread: - * - emit objectDestroyed() right away - * (2) other thread: - * - post information to our thread, emit objectDestroyed() there - * - * pre-conditions: arbitrary thread, lock may or may not be held already - */ -void Probe::objectRemoved(QObject *obj) -{ - QMutexLocker lock(s_lock()); - - if (!isInitialized()) { - IF_DEBUG(cout - << "objectRemoved Before: " - << hex << obj - << " have statics: " << s_listener() << endl;) - - if (!s_listener()) - return; - - QVector &addedBefore = s_listener()->addedBeforeProbeInstance; - for (auto it = addedBefore.begin(); it != addedBefore.end();) { - if (*it == obj) - it = addedBefore.erase(it); - else - ++it; - } - return; - } - - IF_DEBUG(cout << "object removed:" << hex << obj << " " << obj->parent() << endl;) - - bool success = instance()->m_validObjects.remove(obj); - if (!success) { - // object was not tracked by the probe, probably a gammaray object - EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); - return; - } - - instance()->purgeChangesForObject(obj); - EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); - - if (instance()->thread() == QThread::currentThread()) - emit instance()->objectDestroyed(obj); - else - instance()->queueDestroyedObject(obj); -} - -void Probe::handleObjectDestroyed(QObject *obj) -{ - objectRemoved(obj); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::queueCreatedObject(QObject *obj) -{ - EXPENSIVE_ASSERT(!isObjectCreationQueued(obj)); - - ObjectChange c; - c.obj = obj; - c.type = ObjectChange::Create; - m_queuedObjectChanges.push_back(c); - notifyQueuedObjectChanges(); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::queueDestroyedObject(QObject *obj) -{ - ObjectChange c; - c.obj = obj; - c.type = ObjectChange::Destroy; - m_queuedObjectChanges.push_back(c); - notifyQueuedObjectChanges(); -} - -// pre-condition: we have the lock, arbitrary thread -bool Probe::isObjectCreationQueued(QObject *obj) const -{ - return std::find_if(m_queuedObjectChanges.begin(), m_queuedObjectChanges.end(), - [obj](const ObjectChange &c) { - return c.obj == obj && c.type == Probe::ObjectChange::Create; - }) - != m_queuedObjectChanges.end(); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::purgeChangesForObject(QObject *obj) -{ - for (int i = 0; i < m_queuedObjectChanges.size(); ++i) { - if (m_queuedObjectChanges.at(i).obj == obj - && m_queuedObjectChanges.at(i).type == ObjectChange::Create) { - m_queuedObjectChanges.remove(i); - return; - } - } -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::notifyQueuedObjectChanges() -{ - if (m_queueTimer->isActive()) - return; - - if (thread() == QThread::currentThread()) { - m_queueTimer->start(); - } else { - static QMetaMethod m; - if (m.methodIndex() < 0) { - const auto idx = QTimer::staticMetaObject.indexOfMethod("start()"); - Q_ASSERT(idx >= 0); - m = QTimer::staticMetaObject.method(idx); - Q_ASSERT(m.methodIndex() >= 0); - } - m.invoke(m_queueTimer, Qt::QueuedConnection); - } -} - -bool Probe::eventFilter(QObject *receiver, QEvent *event) -{ - if (ProbeGuard::insideProbe() && receiver->thread() == QThread::currentThread()) - return QObject::eventFilter(receiver, event); - - if (event->type() == QEvent::ChildAdded || event->type() == QEvent::ChildRemoved) { - QChildEvent *childEvent = static_cast(event); - QObject *obj = childEvent->child(); - - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(obj); - const bool filtered = filterObject(obj); - - IF_DEBUG(cout << "child event: " << hex << obj << ", p: " << obj->parent() << dec - << ", tracked: " << tracked - << ", filtered: " << filtered - << ", type: " << (childEvent->added() ? "added" : "removed") << endl;) - - if (!filtered && childEvent->added()) { - if (!tracked) { - // was not tracked before, add to all models - // child added events are sent before qt_addObject is called, - // so we assumes this comes from the ctor - objectAdded(obj, true); - } else if (!isObjectCreationQueued(obj) && !isObjectCreationQueued(obj->parent()) && isValidObject(obj->parent())) { - // object is known already, just update the position in the tree - // BUT: only when we did not queue this item before - IF_DEBUG(cout << "update pos: " << hex << obj << endl;) - m_pendingReparents.removeAll(obj); - emit objectReparented(obj); - } else if (!isValidObject(obj->parent())) { - objectAdded(obj->parent()); - m_pendingReparents.push_back(obj); - notifyQueuedObjectChanges(); - } - } else if (tracked) { - // defer processing this until we know its final location - m_pendingReparents.push_back(obj); - notifyQueuedObjectChanges(); - } - } - - // widget only unfortunately, but more precise than ChildAdded/Removed... - if (event->type() == QEvent::ParentChange) { - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(receiver); - const bool filtered = filterObject(receiver); - const bool parentTracked = m_validObjects.contains(receiver->parent()); - - if (!filtered && tracked && !isObjectCreationQueued(receiver) - && !isObjectCreationQueued(receiver->parent()) && parentTracked) { - m_pendingReparents.removeAll(receiver); - emit objectReparented(receiver); - } else if (!parentTracked) { - objectAdded(receiver->parent()); - m_pendingReparents.push_back(receiver); - notifyQueuedObjectChanges(); - } - } - - // we have no preloading hooks, so recover all objects we see - if (needsObjectDiscovery() && event->type() != QEvent::ChildAdded - && event->type() != QEvent::ChildRemoved - && event->type() != QEvent::ParentChange // already handled above - && event->type() != QEvent::Destroy - && event->type() != QEvent::WinIdChange // unsafe since emitted from dtors - && !filterObject(receiver)) { - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(receiver); - if (!tracked) - discoverObject(receiver); - } - - // filters provided by plugins - if (!filterObject(receiver)) { - for (QObject *filter : std::as_const(m_globalEventFilters)) { - filter->eventFilter(receiver, event); - } - } - - return QObject::eventFilter(receiver, event); -} - -void Probe::findExistingObjects() -{ - discoverObject(QCoreApplication::instance()); - - if (auto guiApp = qobject_cast(QCoreApplication::instance())) { - foreach (auto window, guiApp->allWindows()) { - discoverObject(window); - } - } -} - -void Probe::discoverObject(QObject *object) -{ - if (!object) - return; - - QMutexLocker lock(s_lock()); - if (m_validObjects.contains(object)) - return; - - objectAdded(object); - foreach (QObject *child, object->children()) { - discoverObject(child); - } -} - -void Probe::installGlobalEventFilter(QObject *filter) -{ - Q_ASSERT(!m_globalEventFilters.contains(filter)); - m_globalEventFilters.push_back(filter); -} - -bool Probe::needsObjectDiscovery() -{ - return s_listener()->trackDestroyed; -} - -bool Probe::hasReliableObjectTracking() -{ - return true; // qHooks available, which works independent of the injector used -} - -void Probe::selectObject(QObject *object, const QPoint &pos) -{ - const auto tools = m_toolManager->toolsForObject(object); - m_toolManager->selectTool(tools.value(0)); - emit objectSelected(object, pos); -} - -void Probe::selectObject(QObject *object, const QString &toolId, const QPoint &pos) -{ - if (!m_toolManager->hasTool(toolId)) { - std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; - return; - } - - m_toolManager->selectTool(toolId); - emit objectSelected(object, pos); -} - -void Probe::selectObject(void *object, const QString &typeName) -{ - const auto tools = m_toolManager->toolsForObject(object, typeName); - const QString toolId = tools.value(0); - - if (!m_toolManager->hasTool(toolId)) { - std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; - return; - } - - m_toolManager->selectTool(tools.value(0)); - emit nonQObjectSelected(object, typeName); -} - -void Probe::markObjectAsFavorite(QObject *object) -{ - { - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(object)) - return; // deleted in the slot - } - - Q_EMIT objectFavorited(object); -} - -void Probe::removeObjectAsFavorite(QObject *object) -{ - { - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(object)) - return; // deleted in the slot - } - Q_EMIT objectUnfavorited(object); -} - -void Probe::registerSignalSpyCallbackSet(const SignalSpyCallbackSet &callbacks) -{ - if (callbacks.isNull()) - return; - m_signalSpyCallbacks.push_back(callbacks); - setupSignalSpyCallbacks(); -} - -void Probe::setupSignalSpyCallbacks() -{ - // memory management is with us for Qt >= 5.14, therefore static here! - static QSignalSpyCallbackSet cbs = { nullptr, nullptr, nullptr, nullptr }; - foreach (const auto &it, m_signalSpyCallbacks) { - if (it.signalBeginCallback) - cbs.signal_begin_callback = signal_begin_callback; - if (it.signalEndCallback) - cbs.signal_end_callback = signal_end_callback; - if (it.slotBeginCallback) - cbs.slot_begin_callback = slot_begin_callback; - if (it.slotEndCallback) - cbs.slot_end_callback = slot_end_callback; - } - qt_register_signal_spy_callbacks(&cbs); -} - -template -void Probe::executeSignalCallback(const Func &func) -{ - std::for_each(instance()->m_signalSpyCallbacks.constBegin(), - instance()->m_signalSpyCallbacks.constEnd(), - func); -} - -SourceLocation Probe::objectCreationSourceLocation(const QObject *object) -{ - QObject *key = const_cast(object); - if (!s_listener()->constructionBacktracesForObjects.contains(key)) { - IF_DEBUG(std::cout << "No backtrace for object available" << object << "." << std::endl;) - return SourceLocation(); - } - - const auto &st = s_listener()->constructionBacktracesForObjects.value(key); - int distanceToQObject = 0; - - const QMetaObject *metaObject = object->metaObject(); - while (metaObject && metaObject != &QObject::staticMetaObject) { - distanceToQObject++; - metaObject = metaObject->superClass(); - } - - const auto frame = Execution::resolveOne(st, distanceToQObject + 1); - return frame.location; -} - -Execution::Trace Probe::objectCreationStackTrace(QObject *object) -{ - return s_listener()->constructionBacktracesForObjects.value(object); -} diff --git a/.history/core/probe_20260413094137.cpp b/.history/core/probe_20260413094137.cpp deleted file mode 100644 index 85bdda8160..0000000000 --- a/.history/core/probe_20260413094137.cpp +++ /dev/null @@ -1,1007 +0,0 @@ -/* - probe.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ -// krazy:excludeall=null,captruefalse,staticobjects - -#include - -#include "probe.h" -#include "enumrepositoryserver.h" -#include "execution.h" -#include "classesiconsrepositoryserver.h" -#include "metaobjectrepository.h" -#include "objectlistmodel.h" -#include "objecttreemodel.h" -#include "probesettings.h" -#include "probecontroller.h" -#include "problemcollector.h" -#include "toolmanager.h" -#include "toolpluginmodel.h" -#include "util.h" -#include "varianthandler.h" -#include "metaobjectregistry.h" -#include "favoriteobject.h" - -#include "remote/server.h" -#include "remote/remotemodelserver.h" -#include "remote/serverproxymodel.h" -#include "remote/selectionmodelserver.h" -#include "toolpluginerrormodel.h" -#include "probeguard.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define IF_DEBUG(x) - -#ifdef ENABLE_EXPENSIVE_ASSERTS -#define EXPENSIVE_ASSERT(x) Q_ASSERT(x) -#else -#define EXPENSIVE_ASSERT(x) -#endif - -using namespace GammaRay; -using namespace std; - -QAtomicPointer Probe::s_instance = QAtomicPointer(nullptr); - -namespace GammaRay { -static void signal_begin_callback(QObject *caller, int method_index, void **argv) -{ - // Ignore event dispatcher signals - if (caller->inherits("QAbstractEventDispatcher")) - return; - - if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) - return; - - method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.signalBeginCallback) - callbacks.signalBeginCallback(caller, method_index, argv); - }); -} - -static void signal_end_callback(QObject *caller, int method_index) -{ - if (method_index == 0 || !Probe::instance()) - return; - - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()) - return; - if (!Probe::instance()->isValidObject(caller)) // implies filterObject() - return; // deleted in the slot - locker.unlock(); - - method_index = Util::signalIndexToMethodIndex(caller->metaObject(), method_index); - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.signalEndCallback) - callbacks.signalEndCallback(caller, method_index); - }); -} - -static void slot_begin_callback(QObject *caller, int method_index, void **argv) -{ - if (method_index == 0 || !Probe::instance() || Probe::instance()->filterObject(caller)) - return; - - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.slotBeginCallback) - callbacks.slotBeginCallback(caller, method_index, argv); - }); -} - -static void slot_end_callback(QObject *caller, int method_index) -{ - if (method_index == 0 || !Probe::instance()) - return; - - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(caller)) // implies filterObject() - return; // deleted in the slot - locker.unlock(); - - Probe::executeSignalCallback([=](const SignalSpyCallbackSet &callbacks) { - if (callbacks.slotEndCallback) - callbacks.slotEndCallback(caller, method_index); - }); -} - -static QItemSelectionModel *selectionModelFactory(QAbstractItemModel *model) -{ - Q_ASSERT(!model->objectName().isEmpty()); - return new SelectionModelServer(model->objectName() + ".selection", model, Probe::instance()); -} -} - -// useful for debugging, dumps the object and all it's parents -// also usable from GDB! -void dumpObject(QObject *obj) -{ - if (!obj) { - cout << "QObject(0x0)" << endl; - return; - } - - const std::ios::fmtflags oldFlags(cout.flags()); - do { - cout << obj->metaObject()->className() << "(" << hex << obj << ")"; - obj = obj->parent(); - if (obj) - cout << " <- "; - } while (obj); - cout << endl; - cout.flags(oldFlags); -} - -struct Listener -{ - Listener() = default; - - bool trackDestroyed = true; - QVector addedBeforeProbeInstance; - - QHash constructionBacktracesForObjects; -}; - -Q_GLOBAL_STATIC(Listener, s_listener) - -// ensures proper information is returned by isValidObject by -// locking it in objectAdded/Removed -Q_GLOBAL_STATIC(QRecursiveMutex, s_lock) - -Probe::Probe(QObject *parent) - : QObject(parent) - , m_objectListModel(new ObjectListModel(this)) - , m_objectTreeModel(new ObjectTreeModel(this)) - , m_window(nullptr) - , m_metaObjectRegistry(new MetaObjectRegistry(this)) - , m_queueTimer(new QTimer(this)) - , m_server(nullptr) -{ - qputenv("DEBUGINFOD_URLS", QByteArray()); - - Q_ASSERT(thread() == qApp->thread()); - IF_DEBUG(cout << "attaching GammaRay probe" << endl;) - - StreamOperators::registerOperators(); - ProbeSettings::receiveSettings(); - - m_server = new Server(this); - - ObjectBroker::setSelectionModelFactoryCallback(selectionModelFactory); - ObjectBroker::registerObject(new ProbeController(this)); - m_toolManager = new ToolManager(this); - ObjectBroker::registerObject(m_toolManager); - ObjectBroker::registerObject(new FavoriteObject(this)); - - m_problemCollector = new ProblemCollector(this); - - ObjectBroker::registerObject(EnumRepositoryServer::create(this)); - ClassesIconsRepositoryServer::create(this); - registerModel(QStringLiteral("com.kdab.GammaRay.ObjectTree"), m_objectTreeModel); - registerModel(QStringLiteral("com.kdab.GammaRay.ObjectList"), m_objectListModel); - - ToolPluginModel *toolPluginModel = new ToolPluginModel( - m_toolManager->toolPluginManager()->plugins(), this); - registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginModel"), toolPluginModel); - ToolPluginErrorModel *toolPluginErrorModel = new ToolPluginErrorModel(m_toolManager->toolPluginManager()->errors(), this); - registerModel(QStringLiteral("com.kdab.GammaRay.ToolPluginErrorModel"), toolPluginErrorModel); - - m_queueTimer->setSingleShot(true); - m_queueTimer->setInterval(0); - connect(m_queueTimer, &QTimer::timeout, - this, &Probe::processQueuedObjectChanges); - - m_previousSignalSpyCallbackSet = qt_signal_spy_callback_set.loadRelaxed(); - - connect(this, &Probe::objectCreated, m_metaObjectRegistry, &MetaObjectRegistry::objectAdded); - connect(this, &Probe::objectDestroyed, m_metaObjectRegistry, &MetaObjectRegistry::objectRemoved); -} - -Probe::~Probe() -{ - emit aboutToDetach(); - IF_DEBUG(cerr << "detaching GammaRay probe" << endl;) - - // Remove hooks - qtHookData[QHooks::AddQObject] = 0; - qtHookData[QHooks::RemoveQObject] = 0; - qtHookData[QHooks::Startup] = 0; - - qt_register_signal_spy_callbacks(m_previousSignalSpyCallbackSet); - - ObjectBroker::clear(); - ProbeSettings::resetLauncherIdentifier(); - MetaObjectRepository::instance()->clear(); - VariantHandler::clear(); - - s_instance = QAtomicPointer(nullptr); -} - -void Probe::setWindow(QObject *window) -{ - m_window = window; -} - -QObject *Probe::window() const -{ - return m_window; -} - -MetaObjectRegistry *Probe::metaObjectRegistry() const -{ - return m_metaObjectRegistry; -} - -Probe *GammaRay::Probe::instance() -{ - return s_instance.loadRelaxed(); -} - -bool Probe::isInitialized() -{ - return instance(); -} - -bool Probe::canShowWidgets() -{ - return QCoreApplication::instance()->inherits("QApplication"); -} - -void Probe::createProbe(bool findExisting) -{ - Q_ASSERT(qApp); - Q_ASSERT(!isInitialized()); - - // first create the probe and its children - // we must not hold the object lock here as otherwise we can deadlock - // with other QObject's we create and other threads are using. One - // example are QAbstractSocketEngine. - IF_DEBUG(cout << "setting up new probe instance" << endl;) - Probe *probe = nullptr; - { - ProbeGuard guard; - probe = new Probe; - } - IF_DEBUG(cout << "done setting up new probe instance" << endl;) - - connect(qApp, &QCoreApplication::aboutToQuit, probe, &Probe::shutdown); - - // Our safety net, if there's no call to QCoreApplication::exec() we'll never receive the aboutToQuit() signal - // Make sure we still cleanup safely after the application instance got destroyed - connect(qApp, &QObject::destroyed, probe, &Probe::shutdown); - - // now we can get the lock and add items which where added before this point in time - { - QMutexLocker lock(s_lock()); - // now we set the instance while holding the lock, - // all future calls to object{Added,Removed} will - // act directly on the data structures there instead - // of using addedBeforeProbeInstance - // this will only happen _after_ the object lock above is released though - Q_ASSERT(!instance()); - - s_instance = QAtomicPointer(probe); - - // add objects to the probe that were tracked before its creation - foreach (QObject *obj, s_listener()->addedBeforeProbeInstance) { - objectAdded(obj); - } - s_listener()->addedBeforeProbeInstance.clear(); - - // try to find existing objects by other means - if (findExisting) - probe->findExistingObjects(); - } - - // eventually initialize the rest - QMetaObject::invokeMethod(probe, "delayedInit", Qt::QueuedConnection); -} - -void Probe::resendServerAddress() -{ - Q_ASSERT(isInitialized()); - Q_ASSERT(m_server); - if (!m_server->isListening()) // already connected - return; - ProbeSettings::receiveSettings(); - ProbeSettings::sendServerAddress(m_server->externalAddress()); -} - -void Probe::startupHookReceived() -{ -#ifdef Q_OS_ANDROID - QDir root = QDir::home(); - root.cdUp(); - Paths::setRootPath(root.absolutePath()); -#endif - s_listener()->trackDestroyed = false; -} - -void Probe::delayedInit() -{ - QCoreApplication::instance()->installEventFilter(this); - - QString appName = qApp->applicationName(); - if (appName.isEmpty() && !qApp->arguments().isEmpty()) { - appName = qApp->arguments().first().remove(qApp->applicationDirPath()); - if (appName.startsWith('.')) - appName = appName.right(appName.length() - 1); - if (appName.startsWith('/')) - appName = appName.right(appName.length() - 1); - } - if (appName.isEmpty()) - appName = tr("PID %1").arg(qApp->applicationPid()); - m_server->setLabel(appName); - // The applicationName might be translated, so let's go with the application file base name - m_server->setKey(QFileInfo(qApp->applicationFilePath()).completeBaseName()); - m_server->setPid(qApp->applicationPid()); - - if (ProbeSettings::value(QStringLiteral("RemoteAccessEnabled"), true).toBool()) { - const auto serverStarted = m_server->listen(); - if (serverStarted) { - ProbeSettings::sendServerAddress(m_server->externalAddress()); - } else { - ProbeSettings::sendServerLaunchError(m_server->errorString()); - } - } - - if (ProbeSettings::value(QStringLiteral("InProcessUi"), false).toBool()) - showInProcessUi(); -} - -void Probe::shutdown() -{ - delete this; -} - -void Probe::showInProcessUi() -{ - if (!canShowWidgets()) { - cerr << "Unable to show in-process UI in a non-QWidget based application." << endl; - return; - } - - IF_DEBUG(cout << "creating GammaRay::MainWindow" << endl;) - ProbeGuard guard; - - QLibrary lib; - for (auto &path : Paths::pluginPaths(QStringLiteral(GAMMARAY_PROBE_ABI))) { - path += QStringLiteral("/gammaray_inprocessui"); -#if defined(GAMMARAY_INSTALL_QT_LAYOUT) - path += '-'; - path += GAMMARAY_PROBE_ABI; -#else -#if !defined(Q_OS_MAC) -#if defined(QT_DEBUG) - path += QLatin1String(GAMMARAY_DEBUG_POSTFIX); -#endif -#endif -#endif - lib.setFileName(path); - if (lib.load()) - break; - } - - if (!lib.isLoaded()) { - std::cerr << "Failed to load in-process UI module: " - << qPrintable(lib.errorString()) - << std::endl; - } else { - void (*factory)() = reinterpret_cast(lib.resolve("gammaray_create_inprocess_mainwindow")); - if (!factory) - std::cerr << Q_FUNC_INFO << ' ' << qPrintable(lib.errorString()) << endl; - else - factory(); - } - - IF_DEBUG(cout << "creation done" << endl;) -} - -bool Probe::filterObject(QObject *obj) const -{ - QSet visitedObjects; - int iteration = 0; - QObject *o = obj; - do { - if (iteration > 100) { - // Probably we have a loop in the tree. Do loop detection. - if (visitedObjects.contains(o)) { - std::cerr << "We detected a loop in the object tree for object " << o; - if (!o->objectName().isEmpty()) - std::cerr << " \"" << qPrintable(o->objectName()) << "\""; - std::cerr << " (" << o->metaObject()->className() << ")." << std::endl; - return true; - } - visitedObjects << o; - } - ++iteration; - - if (o == this || o == window() || (qstrncmp(o->metaObject()->className(), "GammaRay::", 10) == 0)) { - return true; - } - o = o->parent(); - } while (o); - return false; -} - -void Probe::registerModel(const QString &objectName, QAbstractItemModel *model) -{ - auto *ms = new RemoteModelServer(objectName, model); - ms->setModel(model); - ObjectBroker::registerModelInternal(objectName, model); -} - -QAbstractItemModel *Probe::objectListModel() const -{ - return m_objectListModel; -} - -const QVector &Probe::allQObjects() const -{ - return m_objectListModel->objects(); -} - -QAbstractItemModel *Probe::objectTreeModel() const -{ - return m_objectTreeModel; -} - -ProblemCollector *Probe::problemCollector() const -{ - return m_problemCollector; -} - -QRecursiveMutex *Probe::objectLock() -{ - return s_lock(); -} - -/* - * We need to handle 4 different cases in here: - * (1) our thread, from ctor: - * - wait until next event-loop re-entry of our thread - * - emit objectCreated if object still valid - * (2) our thread, after ctor: - * - emit objectCreated right away - * (3) other thread, from ctor: - * - wait until next event-loop re-entry in other thread (FIXME: we do not currently do this!!) - * - post information to our thread - * (4) other thread, after ctor: - * - post information to our thread - * - emit objectCreated there right away if object still valid - * - * Pre-conditions: lock may or may not be held already, arbitrary thread - */ -void Probe::objectAdded(QObject *obj, bool fromCtor) -{ - if (obj == nullptr) - return; - QMutexLocker lock(s_lock()); - - // attempt to ignore objects created by GammaRay itself, especially short-lived ones - if (fromCtor && ProbeGuard::insideProbe() && obj->thread() == QThread::currentThread()) - return; - - // ignore objects created when global statics are already getting destroyed (on exit) - if (s_listener.isDestroyed()) - return; - - - if (Execution::hasFastStackTrace() && fromCtor) { - s_listener()->constructionBacktracesForObjects.insert(obj, Execution::stackTrace(32, 2)); // skip 2: this and the hook function calling us - } - - if (!isInitialized()) { - IF_DEBUG(cout - << "objectAdded Before: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - s_listener()->addedBeforeProbeInstance << obj; - return; - } - - if (instance()->filterObject(obj)) { - IF_DEBUG(cout - << "objectAdded Filter: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - return; - } - - if (instance()->m_validObjects.contains(obj)) { - // this happens when we get a child event before the objectAdded call from the ctor - // or when we add an item from addedBeforeProbeInstance who got added already - // due to the add-parent-before-child logic - IF_DEBUG(cout - << "objectAdded Known: " - << hex << obj - << (fromCtor ? " (from ctor)" : "") << endl;) - return; - } - - // make sure we already know the parent - if (obj->parent() && !instance()->m_validObjects.contains(obj->parent())) - objectAdded(obj->parent(), fromCtor); - Q_ASSERT(!obj->parent() || instance()->m_validObjects.contains(obj->parent())); - - instance()->m_validObjects << obj; - - if (!fromCtor && obj->parent() && instance()->isObjectCreationQueued(obj->parent())) { - // when a child event triggers a call to objectAdded while inside the ctor - // the parent is already tracked but it's call to objectFullyConstructed - // was delayed. hence we must do the same for the child for integrity - fromCtor = true; - } - - IF_DEBUG(cout << "objectAdded: " << hex << obj - << (fromCtor ? " (from ctor)" : "") - << ", p: " << obj->parent() << endl;) - - if (fromCtor) - instance()->queueCreatedObject(obj); - else - instance()->objectFullyConstructed(obj); -} - -// pre-conditions: lock may or may not be held already, our thread -void Probe::processQueuedObjectChanges() -{ - QMutexLocker lock(s_lock()); - - IF_DEBUG(cout << Q_FUNC_INFO << " " << m_queuedObjectChanges.size() << endl;) - - // must be called from the main thread via timeout - Q_ASSERT(QThread::currentThread() == thread()); - - const auto queuedObjectChanges = m_queuedObjectChanges; // copy, in case this gets modified while we iterate (which can actually happen) - for (const auto &change : queuedObjectChanges) { - switch (change.type) { - case ObjectChange::Create: - objectFullyConstructed(change.obj); - break; - case ObjectChange::Destroy: - emit objectDestroyed(change.obj); - break; - } - } - - IF_DEBUG(cout << Q_FUNC_INFO << " done" << endl;) - - m_queuedObjectChanges.clear(); - - for (QObject *obj : std::as_const(m_pendingReparents)) { - if (!isValidObject(obj)) - continue; - if (filterObject(obj)) // the move might have put it under a hidden parent - objectRemoved(obj); - else - emit objectReparented(obj); - } - m_pendingReparents.clear(); -} - -// pre-condition: lock is held already, our thread -void Probe::objectFullyConstructed(QObject *obj) -{ - Q_ASSERT(thread() == QThread::currentThread()); - - if (!m_validObjects.contains(obj)) { - // deleted already - IF_DEBUG(cout << "stale fully constructed: " << hex << obj << endl;) - return; - } - - if (filterObject(obj)) { - // when the call was delayed from the ctor construction, - // the parent might not have been set properly yet. hence - // apply the filter again - m_validObjects.remove(obj); - IF_DEBUG(cout << "now filtered fully constructed: " << hex << obj << endl;) - return; - } - - IF_DEBUG(cout << "fully constructed: " << hex << obj << endl;) - - // ensure we know all our ancestors already - for (QObject *parent = obj->parent(); parent; parent = parent->parent()) { - if (!m_validObjects.contains(parent)) { - objectAdded(parent); // will also handle any further ancestors - break; - } - } - Q_ASSERT(!obj->parent() || m_validObjects.contains(obj->parent())); - - m_toolManager->objectAdded(obj); - emit objectCreated(obj); -} - -/* - * We have two cases to consider here: - * (1) our thread: - * - emit objectDestroyed() right away - * (2) other thread: - * - post information to our thread, emit objectDestroyed() there - * - * pre-conditions: arbitrary thread, lock may or may not be held already - */ -void Probe::objectRemoved(QObject *obj) -{ - QMutexLocker lock(s_lock()); - - if (!isInitialized()) { - IF_DEBUG(cout - << "objectRemoved Before: " - << hex << obj - << " have statics: " << s_listener() << endl;) - - if (!s_listener()) - return; - - QVector &addedBefore = s_listener()->addedBeforeProbeInstance; - for (auto it = addedBefore.begin(); it != addedBefore.end();) { - if (*it == obj) - it = addedBefore.erase(it); - else - ++it; - } - return; - } - - IF_DEBUG(cout << "object removed:" << hex << obj << " " << obj->parent() << endl;) - - bool success = instance()->m_validObjects.remove(obj); - if (!success) { - // object was not tracked by the probe, probably a gammaray object - EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); - return; - } - - instance()->purgeChangesForObject(obj); - EXPENSIVE_ASSERT(!instance()->isObjectCreationQueued(obj)); - - if (instance()->thread() == QThread::currentThread()) - emit instance()->objectDestroyed(obj); - else - instance()->queueDestroyedObject(obj); -} - -void Probe::handleObjectDestroyed(QObject *obj) -{ - objectRemoved(obj); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::queueCreatedObject(QObject *obj) -{ - EXPENSIVE_ASSERT(!isObjectCreationQueued(obj)); - - ObjectChange c; - c.obj = obj; - c.type = ObjectChange::Create; - m_queuedObjectChanges.push_back(c); - notifyQueuedObjectChanges(); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::queueDestroyedObject(QObject *obj) -{ - ObjectChange c; - c.obj = obj; - c.type = ObjectChange::Destroy; - m_queuedObjectChanges.push_back(c); - notifyQueuedObjectChanges(); -} - -// pre-condition: we have the lock, arbitrary thread -bool Probe::isObjectCreationQueued(QObject *obj) const -{ - return std::find_if(m_queuedObjectChanges.begin(), m_queuedObjectChanges.end(), - [obj](const ObjectChange &c) { - return c.obj == obj && c.type == Probe::ObjectChange::Create; - }) - != m_queuedObjectChanges.end(); -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::purgeChangesForObject(QObject *obj) -{ - for (int i = 0; i < m_queuedObjectChanges.size(); ++i) { - if (m_queuedObjectChanges.at(i).obj == obj - && m_queuedObjectChanges.at(i).type == ObjectChange::Create) { - m_queuedObjectChanges.remove(i); - return; - } - } -} - -// pre-condition: we have the lock, arbitrary thread -void Probe::notifyQueuedObjectChanges() -{ - if (m_queueTimer->isActive()) - return; - - if (thread() == QThread::currentThread()) { - m_queueTimer->start(); - } else { - static QMetaMethod m; - if (m.methodIndex() < 0) { - const auto idx = QTimer::staticMetaObject.indexOfMethod("start()"); - Q_ASSERT(idx >= 0); - m = QTimer::staticMetaObject.method(idx); - Q_ASSERT(m.methodIndex() >= 0); - } - m.invoke(m_queueTimer, Qt::QueuedConnection); - } -} - -bool Probe::eventFilter(QObject *receiver, QEvent *event) -{ - if (ProbeGuard::insideProbe() && receiver->thread() == QThread::currentThread()) - return QObject::eventFilter(receiver, event); - - if (event->type() == QEvent::ChildAdded || event->type() == QEvent::ChildRemoved) { - QChildEvent *childEvent = static_cast(event); - QObject *obj = childEvent->child(); - - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(obj); - const bool filtered = filterObject(obj); - - IF_DEBUG(cout << "child event: " << hex << obj << ", p: " << obj->parent() << dec - << ", tracked: " << tracked - << ", filtered: " << filtered - << ", type: " << (childEvent->added() ? "added" : "removed") << endl;) - - if (!filtered && childEvent->added()) { - if (!tracked) { - // was not tracked before, add to all models - // child added events are sent before qt_addObject is called, - // so we assumes this comes from the ctor - objectAdded(obj, true); - } else if (!isObjectCreationQueued(obj) && !isObjectCreationQueued(obj->parent()) && isValidObject(obj->parent())) { - // object is known already, just update the position in the tree - // BUT: only when we did not queue this item before - IF_DEBUG(cout << "update pos: " << hex << obj << endl;) - m_pendingReparents.removeAll(obj); - emit objectReparented(obj); - } else if (!isValidObject(obj->parent())) { - objectAdded(obj->parent()); - m_pendingReparents.push_back(obj); - notifyQueuedObjectChanges(); - } - } else if (tracked) { - // defer processing this until we know its final location - m_pendingReparents.push_back(obj); - notifyQueuedObjectChanges(); - } - } - - // widget only unfortunately, but more precise than ChildAdded/Removed... - if (event->type() == QEvent::ParentChange) { - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(receiver); - const bool filtered = filterObject(receiver); - const bool parentTracked = m_validObjects.contains(receiver->parent()); - - if (!filtered && tracked && !isObjectCreationQueued(receiver) - && !isObjectCreationQueued(receiver->parent()) && parentTracked) { - m_pendingReparents.removeAll(receiver); - emit objectReparented(receiver); - } else if (!parentTracked) { - objectAdded(receiver->parent()); - m_pendingReparents.push_back(receiver); - notifyQueuedObjectChanges(); - } - } - - // we have no preloading hooks, so recover all objects we see - if (needsObjectDiscovery() && event->type() != QEvent::ChildAdded - && event->type() != QEvent::ChildRemoved - && event->type() != QEvent::ParentChange // already handled above - && event->type() != QEvent::Destroy - && event->type() != QEvent::WinIdChange // unsafe since emitted from dtors - && !filterObject(receiver)) { - QMutexLocker lock(s_lock()); - const bool tracked = m_validObjects.contains(receiver); - if (!tracked) - discoverObject(receiver); - } - - // filters provided by plugins - if (!filterObject(receiver)) { - for (QObject *filter : std::as_const(m_globalEventFilters)) { - filter->eventFilter(receiver, event); - } - } - - return QObject::eventFilter(receiver, event); -} - -void Probe::findExistingObjects() -{ - discoverObject(QCoreApplication::instance()); - - if (auto guiApp = qobject_cast(QCoreApplication::instance())) { - foreach (auto window, guiApp->allWindows()) { - discoverObject(window); - } - } -} - -void Probe::discoverObject(QObject *object) -{ - if (!object) - return; - - QMutexLocker lock(s_lock()); - if (m_validObjects.contains(object)) - return; - - objectAdded(object); - - // foreach (QObject *child, object->children()) { - // discoverObject(child); - // } - - const auto children = object->children(); - for (QObject *child : children) { - if (!child) continue; - if (m_validObjects.contains(child)) continue; - discoverObject(child); - } -} - -void Probe::installGlobalEventFilter(QObject *filter) -{ - Q_ASSERT(!m_globalEventFilters.contains(filter)); - m_globalEventFilters.push_back(filter); -} - -bool Probe::needsObjectDiscovery() -{ - return s_listener()->trackDestroyed; -} - -bool Probe::hasReliableObjectTracking() -{ - return true; // qHooks available, which works independent of the injector used -} - -void Probe::selectObject(QObject *object, const QPoint &pos) -{ - const auto tools = m_toolManager->toolsForObject(object); - m_toolManager->selectTool(tools.value(0)); - emit objectSelected(object, pos); -} - -void Probe::selectObject(QObject *object, const QString &toolId, const QPoint &pos) -{ - if (!m_toolManager->hasTool(toolId)) { - std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; - return; - } - - m_toolManager->selectTool(toolId); - emit objectSelected(object, pos); -} - -void Probe::selectObject(void *object, const QString &typeName) -{ - const auto tools = m_toolManager->toolsForObject(object, typeName); - const QString toolId = tools.value(0); - - if (!m_toolManager->hasTool(toolId)) { - std::cerr << "Invalid tool id: " << qPrintable(toolId) << std::endl; - return; - } - - m_toolManager->selectTool(tools.value(0)); - emit nonQObjectSelected(object, typeName); -} - -void Probe::markObjectAsFavorite(QObject *object) -{ - { - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(object)) - return; // deleted in the slot - } - - Q_EMIT objectFavorited(object); -} - -void Probe::removeObjectAsFavorite(QObject *object) -{ - { - QMutexLocker locker(Probe::objectLock()); - if (!Probe::instance()->isValidObject(object)) - return; // deleted in the slot - } - Q_EMIT objectUnfavorited(object); -} - -void Probe::registerSignalSpyCallbackSet(const SignalSpyCallbackSet &callbacks) -{ - if (callbacks.isNull()) - return; - m_signalSpyCallbacks.push_back(callbacks); - setupSignalSpyCallbacks(); -} - -void Probe::setupSignalSpyCallbacks() -{ - // memory management is with us for Qt >= 5.14, therefore static here! - static QSignalSpyCallbackSet cbs = { nullptr, nullptr, nullptr, nullptr }; - foreach (const auto &it, m_signalSpyCallbacks) { - if (it.signalBeginCallback) - cbs.signal_begin_callback = signal_begin_callback; - if (it.signalEndCallback) - cbs.signal_end_callback = signal_end_callback; - if (it.slotBeginCallback) - cbs.slot_begin_callback = slot_begin_callback; - if (it.slotEndCallback) - cbs.slot_end_callback = slot_end_callback; - } - qt_register_signal_spy_callbacks(&cbs); -} - -template -void Probe::executeSignalCallback(const Func &func) -{ - std::for_each(instance()->m_signalSpyCallbacks.constBegin(), - instance()->m_signalSpyCallbacks.constEnd(), - func); -} - -SourceLocation Probe::objectCreationSourceLocation(const QObject *object) -{ - QObject *key = const_cast(object); - if (!s_listener()->constructionBacktracesForObjects.contains(key)) { - IF_DEBUG(std::cout << "No backtrace for object available" << object << "." << std::endl;) - return SourceLocation(); - } - - const auto &st = s_listener()->constructionBacktracesForObjects.value(key); - int distanceToQObject = 0; - - const QMetaObject *metaObject = object->metaObject(); - while (metaObject && metaObject != &QObject::staticMetaObject) { - distanceToQObject++; - metaObject = metaObject->superClass(); - } - - const auto frame = Execution::resolveOne(st, distanceToQObject + 1); - return frame.location; -} - -Execution::Trace Probe::objectCreationStackTrace(QObject *object) -{ - return s_listener()->constructionBacktracesForObjects.value(object); -} diff --git a/.history/plugins/quickinspector/quickinspector_20260413092956.cpp b/.history/plugins/quickinspector/quickinspector_20260413092956.cpp deleted file mode 100644 index 8a7a7da8be..0000000000 --- a/.history/plugins/quickinspector/quickinspector_20260413092956.cpp +++ /dev/null @@ -1,1190 +0,0 @@ -/* - quickinspector.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ - -#include "quickinspector.h" - -#include "quickanchorspropertyadaptor.h" -#include "quickitemmodel.h" -#include "quickscenegraphmodel.h" -#include "quickscreengrabber.h" -#include "quickpaintanalyzerextension.h" -#include "geometryextension/sggeometryextension.h" -#include "materialextension/materialextension.h" -#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" -#include "textureextension/qsgtexturegrabber.h" -#include "textureextension/textureextension.h" - -#include "quickimplicitbindingdependencyprovider.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include - -Q_DECLARE_METATYPE(QQmlError) - -Q_DECLARE_METATYPE(QQuickItem::Flags) -Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) -Q_DECLARE_METATYPE(QSGNode *) -Q_DECLARE_METATYPE(QSGBasicGeometryNode *) -Q_DECLARE_METATYPE(QSGGeometryNode *) -Q_DECLARE_METATYPE(QSGClipNode *) -Q_DECLARE_METATYPE(QSGTransformNode *) -Q_DECLARE_METATYPE(QSGRootNode *) -Q_DECLARE_METATYPE(QSGOpacityNode *) -Q_DECLARE_METATYPE(QSGNode::Flags) -Q_DECLARE_METATYPE(QSGNode::DirtyState) -Q_DECLARE_METATYPE(QSGGeometry *) -Q_DECLARE_METATYPE(QMatrix4x4 *) -Q_DECLARE_METATYPE(const QMatrix4x4 *) -Q_DECLARE_METATYPE(const QSGClipNode *) -Q_DECLARE_METATYPE(const QSGGeometry *) -Q_DECLARE_METATYPE(QSGMaterial *) -Q_DECLARE_METATYPE(QSGMaterial::Flags) -Q_DECLARE_METATYPE(QSGTexture::WrapMode) -Q_DECLARE_METATYPE(QSGTexture::Filtering) -Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) -Q_DECLARE_METATYPE(QSGRenderNode *) -Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) -Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) -Q_DECLARE_METATYPE(QSGRendererInterface *) -Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) - -using namespace GammaRay; - -#define E(x) \ - { \ - QQuickItem::x, #x \ - } -static const MetaEnum::Value qqitem_flag_table[] = { - E(ItemClipsChildrenToShape), - E(ItemAcceptsInputMethod), - E(ItemIsFocusScope), - E(ItemHasContents), - E(ItemAcceptsDrops) -}; -#undef E - -static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) -{ - QStringList list; - if (hints & QQuickPaintedItem::FastFBOResizing) - list << QStringLiteral("FastFBOResizing"); - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGNode::x, #x \ - } -static const MetaEnum::Value qsg_node_flag_table[] = { - E(OwnedByParent), - E(UsePreprocess), - E(OwnsGeometry), - E(OwnsMaterial), - E(OwnsOpaqueMaterial) -}; - -static const MetaEnum::Value qsg_node_dirtystate_table[] = { - E(DirtySubtreeBlocked), - E(DirtyMatrix), - E(DirtyNodeAdded), - E(DirtyNodeRemoved), - E(DirtyGeometry), - E(DirtyMaterial), - E(DirtyOpacity), - E(DirtyForceUpdate), - E(DirtyUsePreprocess), - E(DirtyPropagationMask) -}; -#undef E - -static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) -{ - QStringList list; -#define F(f) \ - if (flags & QSGMaterial::f) \ - list.push_back(QStringLiteral(#f)); - F(Blending) - F(RequiresDeterminant) - F(RequiresFullMatrixExceptTranslate) - F(RequiresFullMatrix) - F(NoBatching) -#undef F - - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGTexture::x, #x \ - } -static const MetaEnum::Value qsg_texture_anisotropy_table[] = { - E(AnisotropyNone), - E(Anisotropy2x), - E(Anisotropy4x), - E(Anisotropy8x), - E(Anisotropy16x) -}; - -static const MetaEnum::Value qsg_texture_filtering_table[] = { - E(None), - E(Nearest), - E(Linear) -}; - -static const MetaEnum::Value qsg_texture_wrapmode_table[] = { - E(Repeat), - E(ClampToEdge), - E(MirroredRepeat) -}; - -#undef E - -static bool itemHasContents(QQuickItem *item) -{ - return item->flags().testFlag(QQuickItem::ItemHasContents); -} - -static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) -{ - return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); -} - -static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) -{ - switch (customRenderMode) { - case QuickInspectorInterface::VisualizeClipping: - return QByteArray("clip"); - case QuickInspectorInterface::VisualizeOverdraw: - return QByteArray("overdraw"); - case QuickInspectorInterface::VisualizeBatches: - return QByteArray("batches"); - case QuickInspectorInterface::VisualizeChanges: - return QByteArray("changes"); - case QuickInspectorInterface::VisualizeTraces: - case QuickInspectorInterface::NormalRendering: - break; - } - return QByteArray(); -} - -QMutex RenderModeRequest::mutex; - -RenderModeRequest::RenderModeRequest(QObject *parent) - : QObject(parent) - , mode(QuickInspectorInterface::NormalRendering) -{ -} - -RenderModeRequest::~RenderModeRequest() -{ - QMutexLocker lock(&mutex); - - window.clear(); - - if (connection) - disconnect(connection); -} - -void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) -{ - if (toWindow) { - QMutexLocker lock(&mutex); - // Qt does some performance optimizations that break custom render modes. - // Thus the optimizations are only applied if there is no custom render mode set. - // So we need to make the scenegraph recheck whether a custom render mode is set. - // We do this by simply cleaning the scene graph which will recreate the renderer. - // We need however to do that at the proper time from the gui thread. - - if (!connection || (mode != customRenderMode || window != toWindow)) { - if (connection) - disconnect(connection); - mode = customRenderMode; - window = toWindow; - connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); - // trigger window update so afterRendering is emitted - QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); - } - } -} - -void RenderModeRequest::apply() -{ - QMutexLocker lock(&mutex); - - if (connection) - disconnect(connection); - - if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) - return; - - if (window) { - const QByteArray mode = renderModeToString(RenderModeRequest::mode); - QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); - QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { - emit aboutToCleanSceneGraph(); - QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); - winPriv->visualizationMode = mode; - emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); - } - - QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); -} - -void RenderModeRequest::preFinished() -{ - QMutexLocker lock(&mutex); - - if (window) - window->update(); - - emit finished(); -} - -QuickInspector::QuickInspector(Probe *probe, QObject *parent) - : QuickInspectorInterface(parent) - , m_probe(probe) - , m_currentSgNode(nullptr) - , m_itemModel(new QuickItemModel(this)) - , m_sgModel(new QuickSceneGraphModel(this)) - , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), - this)) - , m_sgPropertyController(new PropertyController(QStringLiteral( - "com.kdab.GammaRay.QuickSceneGraph"), - this)) - , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) - , m_pendingRenderMode(new RenderModeRequest(this)) - , m_renderMode(QuickInspectorInterface::NormalRendering) - , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) - , m_slowDownEnabled(false) -{ - registerMetaTypes(); - registerVariantHandlers(); - probe->installGlobalEventFilter(this); - - QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); - windowModel->setSourceModel(probe->objectListModel()); - QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); - proxy->setSourceModel(windowModel); - m_windowModel = proxy; - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); - - auto filterProxy = new ServerProxyModel(this); - filterProxy->setSourceModel(m_itemModel); - filterProxy->addRole(ObjectModel::ObjectIdRole); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); - - if (m_probe->needsObjectDiscovery()) { - connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); - } - - connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); - connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); - connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); - connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); - connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); - connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); - - m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); - connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::itemSelectionChanged); - - auto sgFilterProxy = new ServerProxyModel(this); - sgFilterProxy->setSourceModel(m_sgModel); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); - - m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); - connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::sgSelectionChanged); - connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); - - connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); - connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); - connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); - connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); - connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); - connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); - -#ifndef QT_NO_OPENGL - auto texGrab = new QSGTextureGrabber(this); - connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); -#endif - - connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { - if (m_overlay) - m_overlay->placeOn(ItemOrLayoutFacade()); - }); - - ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", - "QtQuick Item check", - "Warns about items that are visible but out of view.", - &QuickInspector::scanForProblems); - - // needs to be last, extensions require some of the above to be set up correctly - registerPCExtensions(); -} - -QuickInspector::~QuickInspector() -{ - if (m_overlay) { - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - } -} - -void QuickInspector::selectWindow(int index) -{ - const QModelIndex mi = m_windowModel->index(index, 0); - QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); - selectWindow(window); -} - -void QuickInspector::selectWindow(QQuickWindow *window) -{ - if (m_window == window) { - return; - } - - if (m_window) { - const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; - - if (!mode.isEmpty()) { - auto reset = new RenderModeRequest(m_window); - connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); - reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); - } - } - - m_window = window; - m_itemModel->setWindow(window); - m_sgModel->setWindow(window); - m_remoteView->setEventReceiver(m_window); - m_remoteView->resetView(); - recreateOverlay(); - - if (m_window) { - // make sure we have selected something for the property editor to not be entirely empty - selectItem(m_window->contentItem()); - m_window->update(); - } - - checkFeatures(); - - if (m_window) - setCustomRenderMode(m_renderMode); -} - -void QuickInspector::selectItem(QQuickItem *item) -{ - const QAbstractItemModel *model = m_itemSelectionModel->model(); - Model::used(model); - Model::used(m_sgSelectionModel->model()); - - const QModelIndexList indexList = model->match(model->index(0, 0), - ObjectModel::ObjectRole, - QVariant::fromValue(item), 1, - Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_itemSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::selectSGNode(QSGNode *node) -{ - const QAbstractItemModel *model = m_sgSelectionModel->model(); - Model::used(model); - - const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, - QVariant::fromValue( - node), - 1, - Qt::MatchExactly | Qt::MatchRecursive - | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_sgSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::qObjectSelected(QObject *object) -{ - if (auto item = qobject_cast(object)) - selectItem(item); - else if (auto window = qobject_cast(object)) - selectWindow(window); -} - -void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) -{ - auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); - if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) - selectSGNode(reinterpret_cast(object)); -} - -void QuickInspector::objectCreated(QObject *object) -{ - if (QQuickWindow *window = qobject_cast(object)) { - if (QQuickView *view = qobject_cast(object)) { - m_probe->discoverObject(view->engine()); - } else { - QQmlContext *context = QQmlEngine::contextForObject(window); - QQmlEngine *engine = context ? context->engine() : nullptr; - - if (!engine) { - engine = qmlEngine(window->contentItem()->childItems().value(0)); - } - - m_probe->discoverObject(engine); - } - } -} - -void QuickInspector::recreateOverlay() -{ - ProbeGuard guard; - if (m_overlay) - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - - m_overlay = AbstractScreenGrabber::get(m_window); - if (!m_overlay) - return; - - connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); - // the target application might have destroyed the overlay widget - // (e.g. because the parent of the overlay got destroyed). - // just recreate a new one in this case - connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? - // It is for the widget inspector, but for qt quick? - connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); - m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); - - m_remoteView->setGrabberReady(true); -} - -void QuickInspector::aboutToCleanSceneGraph() -{ - m_sgModel->setWindow(nullptr); - m_currentSgNode = nullptr; - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::sceneGraphCleanedUp() -{ - m_sgModel->setWindow(m_window); -} - -void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) -{ - if (!m_window) - return; - RemoteViewFrame frame; - frame.setImage(grabbedFrame.image, grabbedFrame.transform); - frame.setSceneRect(grabbedFrame.itemsGeometryRect); - frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); - if (m_overlay && m_overlay->settings().componentsTraces) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); - else if (!grabbedFrame.itemsGeometry.isEmpty()) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); - m_remoteView->sendFrame(frame); -} - -void QuickInspector::slotGrabWindow() -{ - if (!m_remoteView->isActive() || !m_window) - return; - - Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); - if (m_overlay) { - m_overlay->requestGrabWindow(m_remoteView->userViewport()); - } -} - -void QuickInspector::setCustomRenderMode( - GammaRay::QuickInspectorInterface::RenderMode customRenderMode) -{ - - m_renderMode = customRenderMode; - - m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); - - const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; - if (m_overlay && m_overlay->settings().componentsTraces != tracing) { - auto settings = m_overlay->settings(); - settings.componentsTraces = tracing; - setOverlaySettings(settings); - } -} - -void QuickInspector::checkFeatures() -{ - Features f; - if (!m_window) { - emit features(f); - return; - } - - if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) - f = AllCustomRenderModes; - else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) - f = AnalyzePainting; - - emit features(f); -} - -void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) -{ - if (!m_overlay) { - emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. - return; - } - - m_overlay->setSettings(settings); - emit overlaySettings(m_overlay->settings()); -} - -void QuickInspector::checkOverlaySettings() -{ - emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); -} - -class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer -{ -public: - using QSGAbstractSoftwareRenderer::buildRenderList; - using QSGAbstractSoftwareRenderer::optimizeRenderList; - using QSGAbstractSoftwareRenderer::renderNodes; -}; - -#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) -// keep it working in UBSAN -__attribute__((no_sanitize("vptr"))) -#endif -void QuickInspector::analyzePainting() -{ - if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) - return; - - m_paintAnalyzer->beginAnalyzePainting(); - m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); - { - auto w = QQuickWindowPrivate::get(m_window); - auto renderer = static_cast(w->renderer); - - // this replicates what QSGSoftwareRender is doing - QPainter painter(m_paintAnalyzer->paintDevice()); - painter.setRenderHint(QPainter::Antialiasing); - auto rc = static_cast(w->renderer->context()); - auto prevPainter = rc->m_activePainter; - rc->m_activePainter = &painter; - renderer->markDirty(); - renderer->buildRenderList(); - renderer->optimizeRenderList(); - renderer->renderNodes(&painter); - - rc->m_activePainter = prevPainter; - } - m_paintAnalyzer->endAnalyzePainting(); -} - -void QuickInspector::checkSlowMode() -{ - // We can't check that for now as there is no getter for the property... - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::setSlowMode(bool slow) -{ - if (m_slowDownEnabled == slow) - return; - - static QHash connections; - - m_slowDownEnabled = slow; - - for (int i = 0; i < m_windowModel->rowCount(); ++i) { - const QModelIndex index = m_windowModel->index(i, 0); - QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); - auto it = connections.find(window); - - if (it == connections.end()) { - connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { - auto it = connections.find(window); -#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) - QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); -#else - QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); -#endif - QObject::disconnect(it.value()); - connections.erase(it); }, Qt::DirectConnection)); - } - } - - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::itemSelectionChanged(const QItemSelection &selection) -{ - const QModelIndex index = selection.value(0).topLeft(); - m_currentItem = index.data(ObjectModel::ObjectRole).value(); - m_itemPropertyController->setObject(m_currentItem); - - // It might be that a sg-node is already selected that belongs to this item, but isn't the root - // node of the Item. In this case we don't want to overwrite that selection. - if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { - m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); - const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); - auto proxy = qobject_cast(m_sgSelectionModel->model()); - m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); - } - - if (m_overlay) - m_overlay->placeOn(m_currentItem.data()); -} - -void QuickInspector::sgSelectionChanged(const QItemSelection &selection) -{ - if (selection.isEmpty()) - return; - - const QModelIndex index = selection.first().topLeft(); - m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); - if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) - return; // Apparently the node has been deleted meanwhile, so don't access it. - - void *obj = m_currentSgNode; - auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); - m_sgPropertyController->setObject(m_currentSgNode, mo->className()); - - m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); - selectItem(m_currentItem); -} - -void QuickInspector::sgNodeDeleted(QSGNode *node) -{ - if (m_currentSgNode == node) - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) -{ - if (!m_window) - return; - - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); - - if (!objects.isEmpty()) { - emit elementsAtReceived(objects, bestCandidate); - } -} - -void QuickInspector::pickElementId(const GammaRay::ObjectId &id) -{ - QQuickItem *item = id.asQObjectType(); - if (item) - m_probe->selectObject(item); -} - -QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const -{ - auto rect = parent->childrenRect(); - - const auto childItems = parent->childItems(); - for (const auto child : childItems) { - auto childRect = child->childrenRect(); - - // Get Global positon of childRect - QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); - - // Convert global position to local coordinates of the parent object - QPointF localChildPos = parent->mapFromScene(childGlobalPos); - - // Adjust childRect to be in local coordinates of the parent object - childRect.moveTopLeft(localChildPos.toPoint()); - - // Adding the childRect to the rect - rect = rect.united(childRect); - } - - return rect; -} - -ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, - GammaRay::RemoteViewInterface::RequestMode mode, - int &bestCandidate, bool parentIsGoodCandidate) const -{ - Q_ASSERT(parent); - ObjectIds objects; - - bestCandidate = -1; - if (parentIsGoodCandidate) { - // inherit the parent item opacity when looking for a good candidate item - // i.e. QQuickItem::isVisible is taking the parent into account already, but - // the opacity doesn't - we have to do this manually - // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem - // at least seems to not have this flag set. - parentIsGoodCandidate = isGoodCandidateItem(parent, true); - } - - auto childItems = parent->childItems(); - std::stable_sort(childItems.begin(), childItems.end(), - [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); - - for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order - const auto child = childItems.at(i); - const auto requestedPoint = parent->mapToItem(child, pos); - if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { - const int count = objects.size(); - int bc; // possibly better candidate among subChildren - objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); - - if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { - bestCandidate = count + bc; - } - } - - if (child->contains(requestedPoint)) { - if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { - bestCandidate = objects.size(); - } - objects << ObjectId(child); - } - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - break; - } - } - - if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { - bestCandidate = objects.size(); - } - - objects << ObjectId(parent); - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - objects = ObjectIds() << objects[bestCandidate]; - bestCandidate = 0; - } - - return objects; -} - - -void QuickInspector::scanForProblems() -{ - const QVector &allObjects = Probe::instance()->allQObjects(); - - QMutexLocker lock(Probe::objectLock()); - for (QObject *obj : allObjects) { - QQuickItem *item; - if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) - continue; - - QQuickItem *ancestor = item->parentItem(); - auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); - - while (ancestor && item->window() && ancestor != item->window()->contentItem()) { - if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { - auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); - - if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { - Problem p; - p.severity = Problem::Info; - p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); - p.object = ObjectId(item); - p.locations.push_back(ObjectDataProvider::creationLocation(item)); - p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); - p.findingCategory = Problem::Scan; - ProblemCollector::addProblem(p); - break; - } - } - ancestor = ancestor->parentItem(); - } - } -} - -bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) -{ - if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEv = static_cast(event); - if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { - QQuickWindow *window = qobject_cast(receiver); - if (window && window->contentItem()) { - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), - RemoteViewInterface::RequestBest, bestCandidate); - m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); - } - } - } - - return QObject::eventFilter(receiver, event); -} - -void QuickInspector::registerMetaTypes() -{ - MetaObject *mo = nullptr; - MO_ADD_METAOBJECT1(QQuickWindow, QWindow); - - MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); - MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); - MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); - -#ifndef QT_NO_OPENGL - MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); -#endif - - MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); - MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); - MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); - - MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); - - MO_ADD_METAOBJECT0(QSGRendererInterface); - MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); - - MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); - MO_ADD_PROPERTY_RO(QQuickView, engine); - MO_ADD_PROPERTY_RO(QQuickView, errors); - MO_ADD_PROPERTY_RO(QQuickView, initialSize); - MO_ADD_PROPERTY_RO(QQuickView, rootContext); - MO_ADD_PROPERTY_RO(QQuickView, rootObject); - - MO_ADD_METAOBJECT1(QQuickItem, QObject); - MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); - MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); - MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); - MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); - MO_ADD_PROPERTY(QQuickItem, flags, setFlags); - MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); - MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); - MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); - MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); - MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); - MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); - MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); - MO_ADD_PROPERTY_RO(QQuickItem, window); - - MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); - MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); - MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); - MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); - MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); - - MO_ADD_METAOBJECT1(QSGTexture, QObject); - MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); - MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); - MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); - MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); - MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); - // crashes without a current GL context - // MO_ADD_PROPERTY_RO(QSGTexture, textureId); - MO_ADD_PROPERTY_RO(QSGTexture, textureSize); - MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); - - MO_ADD_METAOBJECT0(QSGNode); - MO_ADD_PROPERTY_RO(QSGNode, parent); - MO_ADD_PROPERTY_RO(QSGNode, childCount); - MO_ADD_PROPERTY_RO(QSGNode, flags); - MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT -#endif - - MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); - MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); - - MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); - MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); - MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); - - MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); - MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); - - MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); - MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); - MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); - - MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); - - MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); - MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); - MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); - - MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); - MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); - MO_ADD_PROPERTY_RO(QSGRenderNode, flags); - MO_ADD_PROPERTY_RO(QSGRenderNode, rect); - MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); - MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); - MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); - - MO_ADD_METAOBJECT0(QSGMaterial); - MO_ADD_PROPERTY_RO(QSGMaterial, flags); - - MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); - - MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); - MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); - - MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); - -#ifndef QT_NO_OPENGL - MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); - - MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); - - MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); -#endif -} - -#define E(x) \ - { \ - QSGRendererInterface::x, #x \ - } -static const MetaEnum::Value qsg_graphics_api_table[] = { - E(Unknown), - E(Software), - E(OpenGL), - E(OpenVG), - E(Direct3D11), - E(Vulkan), - E(Metal), -}; - -static const MetaEnum::Value qsg_shader_compilation_type_table[] = { - E(RuntimeCompilation), - E(OfflineCompilation) -}; - -static const MetaEnum::Value qsg_shader_source_type_table[] = { - E(ShaderSourceString), - E(ShaderSourceFile), - E(ShaderByteCode) -}; - -static const MetaEnum::Value qsg_shader_type_table[] = { - E(UnknownShadingLanguage), - E(GLSL), - E(HLSL) -}; -#undef E - -#define E(x) \ - { \ - QSGRenderNode::x, #x \ - } -static const MetaEnum::Value render_node_state_flags_table[] = { - E(DepthState), - E(StencilState), - E(ScissorState), - E(ColorState), - E(BlendState), - E(CullState), - E(CullState), - E(ViewportState), - E(RenderTargetState) -}; - -static const MetaEnum::Value render_node_rendering_flags_table[] = { - E(BoundedRectRendering), - E(DepthAwareRendering), - E(OpaqueRendering) -}; -#undef E - -static QString anchorLineToString(const QQuickAnchorLine &line) -{ - if (!line.item - || line.anchorLine == QQuickAnchors::InvalidAnchor) { - return QStringLiteral(""); - } - QString s = Util::shortDisplayString(line.item); - switch (line.anchorLine) { - case QQuickAnchors::LeftAnchor: - return s + QStringLiteral(".left"); - case QQuickAnchors::RightAnchor: - return s + QStringLiteral(".right"); - case QQuickAnchors::TopAnchor: - return s + QStringLiteral(".top"); - case QQuickAnchors::BottomAnchor: - return s + QStringLiteral(".bottom"); - case QQuickAnchors::HCenterAnchor: - return s + QStringLiteral(".horizontalCenter"); - case QQuickAnchors::VCenterAnchor: - return s + QStringLiteral(".verticalCenter"); - case QQuickAnchors::BaselineAnchor: - return s + QStringLiteral(".baseline"); - default: - break; - } - return s; -} - -void QuickInspector::registerVariantHandlers() -{ - ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); - ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); - ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); - ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); - ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); - ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); - - VariantHandler::registerStringConverter( - qQuickPaintedItemPerformanceHintsToString); - VariantHandler::registerStringConverter(anchorLineToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(qsgMaterialFlagsToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); - - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); -} - -void QuickInspector::registerPCExtensions() -{ -#ifndef QT_NO_OPENGL - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - - PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); -#endif - PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); - PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); - - BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); -} diff --git a/.history/plugins/quickinspector/quickinspector_20260413093758.cpp b/.history/plugins/quickinspector/quickinspector_20260413093758.cpp deleted file mode 100644 index 1c2dc6abd8..0000000000 --- a/.history/plugins/quickinspector/quickinspector_20260413093758.cpp +++ /dev/null @@ -1,1201 +0,0 @@ -/* - quickinspector.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ - -#include "quickinspector.h" - -#include "quickanchorspropertyadaptor.h" -#include "quickitemmodel.h" -#include "quickscenegraphmodel.h" -#include "quickscreengrabber.h" -#include "quickpaintanalyzerextension.h" -#include "geometryextension/sggeometryextension.h" -#include "materialextension/materialextension.h" -#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" -#include "textureextension/qsgtexturegrabber.h" -#include "textureextension/textureextension.h" - -#include "quickimplicitbindingdependencyprovider.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include - -Q_DECLARE_METATYPE(QQmlError) - -Q_DECLARE_METATYPE(QQuickItem::Flags) -Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) -Q_DECLARE_METATYPE(QSGNode *) -Q_DECLARE_METATYPE(QSGBasicGeometryNode *) -Q_DECLARE_METATYPE(QSGGeometryNode *) -Q_DECLARE_METATYPE(QSGClipNode *) -Q_DECLARE_METATYPE(QSGTransformNode *) -Q_DECLARE_METATYPE(QSGRootNode *) -Q_DECLARE_METATYPE(QSGOpacityNode *) -Q_DECLARE_METATYPE(QSGNode::Flags) -Q_DECLARE_METATYPE(QSGNode::DirtyState) -Q_DECLARE_METATYPE(QSGGeometry *) -Q_DECLARE_METATYPE(QMatrix4x4 *) -Q_DECLARE_METATYPE(const QMatrix4x4 *) -Q_DECLARE_METATYPE(const QSGClipNode *) -Q_DECLARE_METATYPE(const QSGGeometry *) -Q_DECLARE_METATYPE(QSGMaterial *) -Q_DECLARE_METATYPE(QSGMaterial::Flags) -Q_DECLARE_METATYPE(QSGTexture::WrapMode) -Q_DECLARE_METATYPE(QSGTexture::Filtering) -Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) -Q_DECLARE_METATYPE(QSGRenderNode *) -Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) -Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) -Q_DECLARE_METATYPE(QSGRendererInterface *) -Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) - -using namespace GammaRay; - -#define E(x) \ - { \ - QQuickItem::x, #x \ - } -static const MetaEnum::Value qqitem_flag_table[] = { - E(ItemClipsChildrenToShape), - E(ItemAcceptsInputMethod), - E(ItemIsFocusScope), - E(ItemHasContents), - E(ItemAcceptsDrops) -}; -#undef E - -static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) -{ - QStringList list; - if (hints & QQuickPaintedItem::FastFBOResizing) - list << QStringLiteral("FastFBOResizing"); - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGNode::x, #x \ - } -static const MetaEnum::Value qsg_node_flag_table[] = { - E(OwnedByParent), - E(UsePreprocess), - E(OwnsGeometry), - E(OwnsMaterial), - E(OwnsOpaqueMaterial) -}; - -static const MetaEnum::Value qsg_node_dirtystate_table[] = { - E(DirtySubtreeBlocked), - E(DirtyMatrix), - E(DirtyNodeAdded), - E(DirtyNodeRemoved), - E(DirtyGeometry), - E(DirtyMaterial), - E(DirtyOpacity), - E(DirtyForceUpdate), - E(DirtyUsePreprocess), - E(DirtyPropagationMask) -}; -#undef E - -static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) -{ - QStringList list; -#define F(f) \ - if (flags & QSGMaterial::f) \ - list.push_back(QStringLiteral(#f)); - F(Blending) - F(RequiresDeterminant) - F(RequiresFullMatrixExceptTranslate) - F(RequiresFullMatrix) - F(NoBatching) -#undef F - - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGTexture::x, #x \ - } -static const MetaEnum::Value qsg_texture_anisotropy_table[] = { - E(AnisotropyNone), - E(Anisotropy2x), - E(Anisotropy4x), - E(Anisotropy8x), - E(Anisotropy16x) -}; - -static const MetaEnum::Value qsg_texture_filtering_table[] = { - E(None), - E(Nearest), - E(Linear) -}; - -static const MetaEnum::Value qsg_texture_wrapmode_table[] = { - E(Repeat), - E(ClampToEdge), - E(MirroredRepeat) -}; - -#undef E - -static bool itemHasContents(QQuickItem *item) -{ - return item->flags().testFlag(QQuickItem::ItemHasContents); -} - -static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) -{ - return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); -} - -static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) -{ - switch (customRenderMode) { - case QuickInspectorInterface::VisualizeClipping: - return QByteArray("clip"); - case QuickInspectorInterface::VisualizeOverdraw: - return QByteArray("overdraw"); - case QuickInspectorInterface::VisualizeBatches: - return QByteArray("batches"); - case QuickInspectorInterface::VisualizeChanges: - return QByteArray("changes"); - case QuickInspectorInterface::VisualizeTraces: - case QuickInspectorInterface::NormalRendering: - break; - } - return QByteArray(); -} - -QMutex RenderModeRequest::mutex; - -RenderModeRequest::RenderModeRequest(QObject *parent) - : QObject(parent) - , mode(QuickInspectorInterface::NormalRendering) -{ -} - -RenderModeRequest::~RenderModeRequest() -{ - QMutexLocker lock(&mutex); - - window.clear(); - - if (connection) - disconnect(connection); -} - -void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) -{ - if (toWindow) { - QMutexLocker lock(&mutex); - // Qt does some performance optimizations that break custom render modes. - // Thus the optimizations are only applied if there is no custom render mode set. - // So we need to make the scenegraph recheck whether a custom render mode is set. - // We do this by simply cleaning the scene graph which will recreate the renderer. - // We need however to do that at the proper time from the gui thread. - - if (!connection || (mode != customRenderMode || window != toWindow)) { - if (connection) - disconnect(connection); - mode = customRenderMode; - window = toWindow; - connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); - // trigger window update so afterRendering is emitted - QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); - } - } -} - -void RenderModeRequest::apply() -{ - QMutexLocker lock(&mutex); - - if (connection) - disconnect(connection); - - if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) - return; - - if (window) { - const QByteArray mode = renderModeToString(RenderModeRequest::mode); - QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); - QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { - emit aboutToCleanSceneGraph(); - QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); - winPriv->visualizationMode = mode; - emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); - } - - QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); -} - -void RenderModeRequest::preFinished() -{ - QMutexLocker lock(&mutex); - - if (window) - window->update(); - - emit finished(); -} - -QuickInspector::QuickInspector(Probe *probe, QObject *parent) - : QuickInspectorInterface(parent) - , m_probe(probe) - , m_currentSgNode(nullptr) - , m_itemModel(new QuickItemModel(this)) - , m_sgModel(new QuickSceneGraphModel(this)) - , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), - this)) - , m_sgPropertyController(new PropertyController(QStringLiteral( - "com.kdab.GammaRay.QuickSceneGraph"), - this)) - , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) - , m_pendingRenderMode(new RenderModeRequest(this)) - , m_renderMode(QuickInspectorInterface::NormalRendering) - , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) - , m_slowDownEnabled(false) -{ - registerMetaTypes(); - registerVariantHandlers(); - probe->installGlobalEventFilter(this); - - QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); - windowModel->setSourceModel(probe->objectListModel()); - QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); - proxy->setSourceModel(windowModel); - m_windowModel = proxy; - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); - - auto filterProxy = new ServerProxyModel(this); - filterProxy->setSourceModel(m_itemModel); - filterProxy->addRole(ObjectModel::ObjectIdRole); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); - - if (m_probe->needsObjectDiscovery()) { - connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); - } - - connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); - connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); - connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); - connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); - connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); - connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); - - m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); - connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::itemSelectionChanged); - - auto sgFilterProxy = new ServerProxyModel(this); - sgFilterProxy->setSourceModel(m_sgModel); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); - - m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); - connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::sgSelectionChanged); - connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); - - connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); - connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); - connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); - connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); - connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); - connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); - -#ifndef QT_NO_OPENGL - auto texGrab = new QSGTextureGrabber(this); - connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); -#endif - - connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { - if (m_overlay) - m_overlay->placeOn(ItemOrLayoutFacade()); - }); - - ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", - "QtQuick Item check", - "Warns about items that are visible but out of view.", - &QuickInspector::scanForProblems); - - // needs to be last, extensions require some of the above to be set up correctly - registerPCExtensions(); -} - -QuickInspector::~QuickInspector() -{ - if (m_overlay) { - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - } -} - -void QuickInspector::selectWindow(int index) -{ - const QModelIndex mi = m_windowModel->index(index, 0); - QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); - selectWindow(window); -} - -void QuickInspector::selectWindow(QQuickWindow *window) -{ - if (m_window == window) { - return; - } - - if (m_window) { - const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; - - if (!mode.isEmpty()) { - auto reset = new RenderModeRequest(m_window); - connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); - reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); - } - } - - m_window = window; - m_itemModel->setWindow(window); - m_sgModel->setWindow(window); - m_remoteView->setEventReceiver(m_window); - m_remoteView->resetView(); - recreateOverlay(); - - if (m_window) { - // make sure we have selected something for the property editor to not be entirely empty - selectItem(m_window->contentItem()); - m_window->update(); - } - - checkFeatures(); - - if (m_window) - setCustomRenderMode(m_renderMode); -} - -void QuickInspector::selectItem(QQuickItem *item) -{ - const QAbstractItemModel *model = m_itemSelectionModel->model(); - Model::used(model); - Model::used(m_sgSelectionModel->model()); - - const QModelIndexList indexList = model->match(model->index(0, 0), - ObjectModel::ObjectRole, - QVariant::fromValue(item), 1, - Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_itemSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::selectSGNode(QSGNode *node) -{ - const QAbstractItemModel *model = m_sgSelectionModel->model(); - Model::used(model); - - const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, - QVariant::fromValue( - node), - 1, - Qt::MatchExactly | Qt::MatchRecursive - | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_sgSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::qObjectSelected(QObject *object) -{ - if (auto item = qobject_cast(object)) - selectItem(item); - else if (auto window = qobject_cast(object)) - selectWindow(window); -} - -void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) -{ - auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); - if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) - selectSGNode(reinterpret_cast(object)); -} - -void QuickInspector::objectCreated(QObject *object) -{ - if (QQuickWindow *window = qobject_cast(object)) { - if (QQuickView *view = qobject_cast(object)) { - if (view->engine()) { - m_probe->discoverObject(view->engine()); - } - } else { - QQmlContext *context = QQmlEngine::contextForObject(window); - QQmlEngine *engine = context ? context->engine() : nullptr; - - if (!engine) { - // engine = qmlEngine(window->contentItem()->childItems().value(0)); - QQuickItem *contentItem = window->contentItem(); - if (contentItem) { - const auto children = contentItem->childItems(); - if (!children.isEmpty()) { - engine = qmlEngine(children.first()); - } - } - } - - if (engine) { - m_probe->discoverObject(engine); - } - } - } -} - -void QuickInspector::recreateOverlay() -{ - ProbeGuard guard; - if (m_overlay) - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - - m_overlay = AbstractScreenGrabber::get(m_window); - if (!m_overlay) - return; - - connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); - // the target application might have destroyed the overlay widget - // (e.g. because the parent of the overlay got destroyed). - // just recreate a new one in this case - connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? - // It is for the widget inspector, but for qt quick? - connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); - m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); - - m_remoteView->setGrabberReady(true); -} - -void QuickInspector::aboutToCleanSceneGraph() -{ - m_sgModel->setWindow(nullptr); - m_currentSgNode = nullptr; - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::sceneGraphCleanedUp() -{ - m_sgModel->setWindow(m_window); -} - -void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) -{ - if (!m_window) - return; - RemoteViewFrame frame; - frame.setImage(grabbedFrame.image, grabbedFrame.transform); - frame.setSceneRect(grabbedFrame.itemsGeometryRect); - frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); - if (m_overlay && m_overlay->settings().componentsTraces) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); - else if (!grabbedFrame.itemsGeometry.isEmpty()) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); - m_remoteView->sendFrame(frame); -} - -void QuickInspector::slotGrabWindow() -{ - if (!m_remoteView->isActive() || !m_window) - return; - - Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); - if (m_overlay) { - m_overlay->requestGrabWindow(m_remoteView->userViewport()); - } -} - -void QuickInspector::setCustomRenderMode( - GammaRay::QuickInspectorInterface::RenderMode customRenderMode) -{ - - m_renderMode = customRenderMode; - - m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); - - const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; - if (m_overlay && m_overlay->settings().componentsTraces != tracing) { - auto settings = m_overlay->settings(); - settings.componentsTraces = tracing; - setOverlaySettings(settings); - } -} - -void QuickInspector::checkFeatures() -{ - Features f; - if (!m_window) { - emit features(f); - return; - } - - if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) - f = AllCustomRenderModes; - else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) - f = AnalyzePainting; - - emit features(f); -} - -void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) -{ - if (!m_overlay) { - emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. - return; - } - - m_overlay->setSettings(settings); - emit overlaySettings(m_overlay->settings()); -} - -void QuickInspector::checkOverlaySettings() -{ - emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); -} - -class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer -{ -public: - using QSGAbstractSoftwareRenderer::buildRenderList; - using QSGAbstractSoftwareRenderer::optimizeRenderList; - using QSGAbstractSoftwareRenderer::renderNodes; -}; - -#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) -// keep it working in UBSAN -__attribute__((no_sanitize("vptr"))) -#endif -void QuickInspector::analyzePainting() -{ - if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) - return; - - m_paintAnalyzer->beginAnalyzePainting(); - m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); - { - auto w = QQuickWindowPrivate::get(m_window); - auto renderer = static_cast(w->renderer); - - // this replicates what QSGSoftwareRender is doing - QPainter painter(m_paintAnalyzer->paintDevice()); - painter.setRenderHint(QPainter::Antialiasing); - auto rc = static_cast(w->renderer->context()); - auto prevPainter = rc->m_activePainter; - rc->m_activePainter = &painter; - renderer->markDirty(); - renderer->buildRenderList(); - renderer->optimizeRenderList(); - renderer->renderNodes(&painter); - - rc->m_activePainter = prevPainter; - } - m_paintAnalyzer->endAnalyzePainting(); -} - -void QuickInspector::checkSlowMode() -{ - // We can't check that for now as there is no getter for the property... - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::setSlowMode(bool slow) -{ - if (m_slowDownEnabled == slow) - return; - - static QHash connections; - - m_slowDownEnabled = slow; - - for (int i = 0; i < m_windowModel->rowCount(); ++i) { - const QModelIndex index = m_windowModel->index(i, 0); - QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); - auto it = connections.find(window); - - if (it == connections.end()) { - connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { - auto it = connections.find(window); -#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) - QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); -#else - QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); -#endif - QObject::disconnect(it.value()); - connections.erase(it); }, Qt::DirectConnection)); - } - } - - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::itemSelectionChanged(const QItemSelection &selection) -{ - const QModelIndex index = selection.value(0).topLeft(); - m_currentItem = index.data(ObjectModel::ObjectRole).value(); - m_itemPropertyController->setObject(m_currentItem); - - // It might be that a sg-node is already selected that belongs to this item, but isn't the root - // node of the Item. In this case we don't want to overwrite that selection. - if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { - m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); - const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); - auto proxy = qobject_cast(m_sgSelectionModel->model()); - m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); - } - - if (m_overlay) - m_overlay->placeOn(m_currentItem.data()); -} - -void QuickInspector::sgSelectionChanged(const QItemSelection &selection) -{ - if (selection.isEmpty()) - return; - - const QModelIndex index = selection.first().topLeft(); - m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); - if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) - return; // Apparently the node has been deleted meanwhile, so don't access it. - - void *obj = m_currentSgNode; - auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); - m_sgPropertyController->setObject(m_currentSgNode, mo->className()); - - m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); - selectItem(m_currentItem); -} - -void QuickInspector::sgNodeDeleted(QSGNode *node) -{ - if (m_currentSgNode == node) - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) -{ - if (!m_window) - return; - - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); - - if (!objects.isEmpty()) { - emit elementsAtReceived(objects, bestCandidate); - } -} - -void QuickInspector::pickElementId(const GammaRay::ObjectId &id) -{ - QQuickItem *item = id.asQObjectType(); - if (item) - m_probe->selectObject(item); -} - -QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const -{ - auto rect = parent->childrenRect(); - - const auto childItems = parent->childItems(); - for (const auto child : childItems) { - auto childRect = child->childrenRect(); - - // Get Global positon of childRect - QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); - - // Convert global position to local coordinates of the parent object - QPointF localChildPos = parent->mapFromScene(childGlobalPos); - - // Adjust childRect to be in local coordinates of the parent object - childRect.moveTopLeft(localChildPos.toPoint()); - - // Adding the childRect to the rect - rect = rect.united(childRect); - } - - return rect; -} - -ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, - GammaRay::RemoteViewInterface::RequestMode mode, - int &bestCandidate, bool parentIsGoodCandidate) const -{ - Q_ASSERT(parent); - ObjectIds objects; - - bestCandidate = -1; - if (parentIsGoodCandidate) { - // inherit the parent item opacity when looking for a good candidate item - // i.e. QQuickItem::isVisible is taking the parent into account already, but - // the opacity doesn't - we have to do this manually - // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem - // at least seems to not have this flag set. - parentIsGoodCandidate = isGoodCandidateItem(parent, true); - } - - auto childItems = parent->childItems(); - std::stable_sort(childItems.begin(), childItems.end(), - [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); - - for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order - const auto child = childItems.at(i); - const auto requestedPoint = parent->mapToItem(child, pos); - if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { - const int count = objects.size(); - int bc; // possibly better candidate among subChildren - objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); - - if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { - bestCandidate = count + bc; - } - } - - if (child->contains(requestedPoint)) { - if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { - bestCandidate = objects.size(); - } - objects << ObjectId(child); - } - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - break; - } - } - - if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { - bestCandidate = objects.size(); - } - - objects << ObjectId(parent); - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - objects = ObjectIds() << objects[bestCandidate]; - bestCandidate = 0; - } - - return objects; -} - - -void QuickInspector::scanForProblems() -{ - const QVector &allObjects = Probe::instance()->allQObjects(); - - QMutexLocker lock(Probe::objectLock()); - for (QObject *obj : allObjects) { - QQuickItem *item; - if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) - continue; - - QQuickItem *ancestor = item->parentItem(); - auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); - - while (ancestor && item->window() && ancestor != item->window()->contentItem()) { - if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { - auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); - - if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { - Problem p; - p.severity = Problem::Info; - p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); - p.object = ObjectId(item); - p.locations.push_back(ObjectDataProvider::creationLocation(item)); - p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); - p.findingCategory = Problem::Scan; - ProblemCollector::addProblem(p); - break; - } - } - ancestor = ancestor->parentItem(); - } - } -} - -bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) -{ - if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEv = static_cast(event); - if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { - QQuickWindow *window = qobject_cast(receiver); - if (window && window->contentItem()) { - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), - RemoteViewInterface::RequestBest, bestCandidate); - m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); - } - } - } - - return QObject::eventFilter(receiver, event); -} - -void QuickInspector::registerMetaTypes() -{ - MetaObject *mo = nullptr; - MO_ADD_METAOBJECT1(QQuickWindow, QWindow); - - MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); - MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); - MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); - -#ifndef QT_NO_OPENGL - MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); -#endif - - MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); - MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); - MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); - - MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); - - MO_ADD_METAOBJECT0(QSGRendererInterface); - MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); - - MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); - MO_ADD_PROPERTY_RO(QQuickView, engine); - MO_ADD_PROPERTY_RO(QQuickView, errors); - MO_ADD_PROPERTY_RO(QQuickView, initialSize); - MO_ADD_PROPERTY_RO(QQuickView, rootContext); - MO_ADD_PROPERTY_RO(QQuickView, rootObject); - - MO_ADD_METAOBJECT1(QQuickItem, QObject); - MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); - MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); - MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); - MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); - MO_ADD_PROPERTY(QQuickItem, flags, setFlags); - MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); - MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); - MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); - MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); - MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); - MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); - MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); - MO_ADD_PROPERTY_RO(QQuickItem, window); - - MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); - MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); - MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); - MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); - MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); - - MO_ADD_METAOBJECT1(QSGTexture, QObject); - MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); - MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); - MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); - MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); - MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); - // crashes without a current GL context - // MO_ADD_PROPERTY_RO(QSGTexture, textureId); - MO_ADD_PROPERTY_RO(QSGTexture, textureSize); - MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); - - MO_ADD_METAOBJECT0(QSGNode); - MO_ADD_PROPERTY_RO(QSGNode, parent); - MO_ADD_PROPERTY_RO(QSGNode, childCount); - MO_ADD_PROPERTY_RO(QSGNode, flags); - MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT -#endif - - MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); - MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); - - MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); - MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); - MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); - - MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); - MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); - - MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); - MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); - MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); - - MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); - - MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); - MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); - MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); - - MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); - MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); - MO_ADD_PROPERTY_RO(QSGRenderNode, flags); - MO_ADD_PROPERTY_RO(QSGRenderNode, rect); - MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); - MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); - MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); - - MO_ADD_METAOBJECT0(QSGMaterial); - MO_ADD_PROPERTY_RO(QSGMaterial, flags); - - MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); - - MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); - MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); - - MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); - -#ifndef QT_NO_OPENGL - MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); - - MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); - - MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); -#endif -} - -#define E(x) \ - { \ - QSGRendererInterface::x, #x \ - } -static const MetaEnum::Value qsg_graphics_api_table[] = { - E(Unknown), - E(Software), - E(OpenGL), - E(OpenVG), - E(Direct3D11), - E(Vulkan), - E(Metal), -}; - -static const MetaEnum::Value qsg_shader_compilation_type_table[] = { - E(RuntimeCompilation), - E(OfflineCompilation) -}; - -static const MetaEnum::Value qsg_shader_source_type_table[] = { - E(ShaderSourceString), - E(ShaderSourceFile), - E(ShaderByteCode) -}; - -static const MetaEnum::Value qsg_shader_type_table[] = { - E(UnknownShadingLanguage), - E(GLSL), - E(HLSL) -}; -#undef E - -#define E(x) \ - { \ - QSGRenderNode::x, #x \ - } -static const MetaEnum::Value render_node_state_flags_table[] = { - E(DepthState), - E(StencilState), - E(ScissorState), - E(ColorState), - E(BlendState), - E(CullState), - E(CullState), - E(ViewportState), - E(RenderTargetState) -}; - -static const MetaEnum::Value render_node_rendering_flags_table[] = { - E(BoundedRectRendering), - E(DepthAwareRendering), - E(OpaqueRendering) -}; -#undef E - -static QString anchorLineToString(const QQuickAnchorLine &line) -{ - if (!line.item - || line.anchorLine == QQuickAnchors::InvalidAnchor) { - return QStringLiteral(""); - } - QString s = Util::shortDisplayString(line.item); - switch (line.anchorLine) { - case QQuickAnchors::LeftAnchor: - return s + QStringLiteral(".left"); - case QQuickAnchors::RightAnchor: - return s + QStringLiteral(".right"); - case QQuickAnchors::TopAnchor: - return s + QStringLiteral(".top"); - case QQuickAnchors::BottomAnchor: - return s + QStringLiteral(".bottom"); - case QQuickAnchors::HCenterAnchor: - return s + QStringLiteral(".horizontalCenter"); - case QQuickAnchors::VCenterAnchor: - return s + QStringLiteral(".verticalCenter"); - case QQuickAnchors::BaselineAnchor: - return s + QStringLiteral(".baseline"); - default: - break; - } - return s; -} - -void QuickInspector::registerVariantHandlers() -{ - ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); - ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); - ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); - ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); - ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); - ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); - - VariantHandler::registerStringConverter( - qQuickPaintedItemPerformanceHintsToString); - VariantHandler::registerStringConverter(anchorLineToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(qsgMaterialFlagsToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); - - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); -} - -void QuickInspector::registerPCExtensions() -{ -#ifndef QT_NO_OPENGL - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - - PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); -#endif - PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); - PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); - - BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); -} diff --git a/.history/plugins/quickinspector/quickinspector_20260413093955.cpp b/.history/plugins/quickinspector/quickinspector_20260413093955.cpp deleted file mode 100644 index 1c2dc6abd8..0000000000 --- a/.history/plugins/quickinspector/quickinspector_20260413093955.cpp +++ /dev/null @@ -1,1201 +0,0 @@ -/* - quickinspector.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2014 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ - -#include "quickinspector.h" - -#include "quickanchorspropertyadaptor.h" -#include "quickitemmodel.h" -#include "quickscenegraphmodel.h" -#include "quickscreengrabber.h" -#include "quickpaintanalyzerextension.h" -#include "geometryextension/sggeometryextension.h" -#include "materialextension/materialextension.h" -#include "materialextension/qquickopenglshadereffectmaterialadaptor.h" -#include "textureextension/qsgtexturegrabber.h" -#include "textureextension/textureextension.h" - -#include "quickimplicitbindingdependencyprovider.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#ifndef QT_NO_OPENGL -#include -#endif -#include -#include - -Q_DECLARE_METATYPE(QQmlError) - -Q_DECLARE_METATYPE(QQuickItem::Flags) -Q_DECLARE_METATYPE(QQuickPaintedItem::PerformanceHints) -Q_DECLARE_METATYPE(QSGNode *) -Q_DECLARE_METATYPE(QSGBasicGeometryNode *) -Q_DECLARE_METATYPE(QSGGeometryNode *) -Q_DECLARE_METATYPE(QSGClipNode *) -Q_DECLARE_METATYPE(QSGTransformNode *) -Q_DECLARE_METATYPE(QSGRootNode *) -Q_DECLARE_METATYPE(QSGOpacityNode *) -Q_DECLARE_METATYPE(QSGNode::Flags) -Q_DECLARE_METATYPE(QSGNode::DirtyState) -Q_DECLARE_METATYPE(QSGGeometry *) -Q_DECLARE_METATYPE(QMatrix4x4 *) -Q_DECLARE_METATYPE(const QMatrix4x4 *) -Q_DECLARE_METATYPE(const QSGClipNode *) -Q_DECLARE_METATYPE(const QSGGeometry *) -Q_DECLARE_METATYPE(QSGMaterial *) -Q_DECLARE_METATYPE(QSGMaterial::Flags) -Q_DECLARE_METATYPE(QSGTexture::WrapMode) -Q_DECLARE_METATYPE(QSGTexture::Filtering) -Q_DECLARE_METATYPE(QSGTexture::AnisotropyLevel) -Q_DECLARE_METATYPE(QSGRenderNode *) -Q_DECLARE_METATYPE(QSGRenderNode::RenderingFlags) -Q_DECLARE_METATYPE(QSGRenderNode::StateFlags) -Q_DECLARE_METATYPE(QSGRendererInterface *) -Q_DECLARE_METATYPE(QSGRendererInterface::GraphicsApi) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderCompilationTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderSourceTypes) -Q_DECLARE_METATYPE(QSGRendererInterface::ShaderType) - -using namespace GammaRay; - -#define E(x) \ - { \ - QQuickItem::x, #x \ - } -static const MetaEnum::Value qqitem_flag_table[] = { - E(ItemClipsChildrenToShape), - E(ItemAcceptsInputMethod), - E(ItemIsFocusScope), - E(ItemHasContents), - E(ItemAcceptsDrops) -}; -#undef E - -static QString qQuickPaintedItemPerformanceHintsToString(QQuickPaintedItem::PerformanceHints hints) -{ - QStringList list; - if (hints & QQuickPaintedItem::FastFBOResizing) - list << QStringLiteral("FastFBOResizing"); - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGNode::x, #x \ - } -static const MetaEnum::Value qsg_node_flag_table[] = { - E(OwnedByParent), - E(UsePreprocess), - E(OwnsGeometry), - E(OwnsMaterial), - E(OwnsOpaqueMaterial) -}; - -static const MetaEnum::Value qsg_node_dirtystate_table[] = { - E(DirtySubtreeBlocked), - E(DirtyMatrix), - E(DirtyNodeAdded), - E(DirtyNodeRemoved), - E(DirtyGeometry), - E(DirtyMaterial), - E(DirtyOpacity), - E(DirtyForceUpdate), - E(DirtyUsePreprocess), - E(DirtyPropagationMask) -}; -#undef E - -static QString qsgMaterialFlagsToString(QSGMaterial::Flags flags) -{ - QStringList list; -#define F(f) \ - if (flags & QSGMaterial::f) \ - list.push_back(QStringLiteral(#f)); - F(Blending) - F(RequiresDeterminant) - F(RequiresFullMatrixExceptTranslate) - F(RequiresFullMatrix) - F(NoBatching) -#undef F - - if (list.isEmpty()) - return QStringLiteral(""); - return list.join(QStringLiteral(" | ")); -} - -#define E(x) \ - { \ - QSGTexture::x, #x \ - } -static const MetaEnum::Value qsg_texture_anisotropy_table[] = { - E(AnisotropyNone), - E(Anisotropy2x), - E(Anisotropy4x), - E(Anisotropy8x), - E(Anisotropy16x) -}; - -static const MetaEnum::Value qsg_texture_filtering_table[] = { - E(None), - E(Nearest), - E(Linear) -}; - -static const MetaEnum::Value qsg_texture_wrapmode_table[] = { - E(Repeat), - E(ClampToEdge), - E(MirroredRepeat) -}; - -#undef E - -static bool itemHasContents(QQuickItem *item) -{ - return item->flags().testFlag(QQuickItem::ItemHasContents); -} - -static bool isGoodCandidateItem(QQuickItem *item, bool ignoreItemHasContents = false) -{ - return !(!item->isVisible() || qFuzzyCompare(item->opacity() + qreal(1.0), qreal(1.0)) || (!ignoreItemHasContents && !itemHasContents(item))); -} - -static QByteArray renderModeToString(QuickInspectorInterface::RenderMode customRenderMode) -{ - switch (customRenderMode) { - case QuickInspectorInterface::VisualizeClipping: - return QByteArray("clip"); - case QuickInspectorInterface::VisualizeOverdraw: - return QByteArray("overdraw"); - case QuickInspectorInterface::VisualizeBatches: - return QByteArray("batches"); - case QuickInspectorInterface::VisualizeChanges: - return QByteArray("changes"); - case QuickInspectorInterface::VisualizeTraces: - case QuickInspectorInterface::NormalRendering: - break; - } - return QByteArray(); -} - -QMutex RenderModeRequest::mutex; - -RenderModeRequest::RenderModeRequest(QObject *parent) - : QObject(parent) - , mode(QuickInspectorInterface::NormalRendering) -{ -} - -RenderModeRequest::~RenderModeRequest() -{ - QMutexLocker lock(&mutex); - - window.clear(); - - if (connection) - disconnect(connection); -} - -void RenderModeRequest::applyOrDelay(QQuickWindow *toWindow, QuickInspectorInterface::RenderMode customRenderMode) -{ - if (toWindow) { - QMutexLocker lock(&mutex); - // Qt does some performance optimizations that break custom render modes. - // Thus the optimizations are only applied if there is no custom render mode set. - // So we need to make the scenegraph recheck whether a custom render mode is set. - // We do this by simply cleaning the scene graph which will recreate the renderer. - // We need however to do that at the proper time from the gui thread. - - if (!connection || (mode != customRenderMode || window != toWindow)) { - if (connection) - disconnect(connection); - mode = customRenderMode; - window = toWindow; - connection = connect(window.data(), &QQuickWindow::afterRendering, this, &RenderModeRequest::apply, Qt::DirectConnection); - // trigger window update so afterRendering is emitted - QMetaObject::invokeMethod(window, "update", Qt::QueuedConnection); - } - } -} - -void RenderModeRequest::apply() -{ - QMutexLocker lock(&mutex); - - if (connection) - disconnect(connection); - - if (window && window->rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) - return; - - if (window) { - const QByteArray mode = renderModeToString(RenderModeRequest::mode); - QQuickWindowPrivate *winPriv = QQuickWindowPrivate::get(window); - QObject::connect(window.get(), &QQuickWindow::beforeSynchronizing, this, [this, winPriv, mode]() { - emit aboutToCleanSceneGraph(); - QMetaObject::invokeMethod(window, "cleanupSceneGraph", Qt::DirectConnection); - winPriv->visualizationMode = mode; - emit sceneGraphCleanedUp(); }, static_cast(Qt::DirectConnection | Qt::SingleShotConnection)); - } - - QMetaObject::invokeMethod(this, "preFinished", Qt::QueuedConnection); -} - -void RenderModeRequest::preFinished() -{ - QMutexLocker lock(&mutex); - - if (window) - window->update(); - - emit finished(); -} - -QuickInspector::QuickInspector(Probe *probe, QObject *parent) - : QuickInspectorInterface(parent) - , m_probe(probe) - , m_currentSgNode(nullptr) - , m_itemModel(new QuickItemModel(this)) - , m_sgModel(new QuickSceneGraphModel(this)) - , m_itemPropertyController(new PropertyController(QStringLiteral("com.kdab.GammaRay.QuickItem"), - this)) - , m_sgPropertyController(new PropertyController(QStringLiteral( - "com.kdab.GammaRay.QuickSceneGraph"), - this)) - , m_remoteView(new RemoteViewServer(QStringLiteral("com.kdab.GammaRay.QuickRemoteView"), this)) - , m_pendingRenderMode(new RenderModeRequest(this)) - , m_renderMode(QuickInspectorInterface::NormalRendering) - , m_paintAnalyzer(new PaintAnalyzer(QStringLiteral("com.kdab.GammaRay.QuickPaintAnalyzer"), this)) - , m_slowDownEnabled(false) -{ - registerMetaTypes(); - registerVariantHandlers(); - probe->installGlobalEventFilter(this); - - QAbstractProxyModel *windowModel = new ObjectTypeFilterProxyModel(this); - windowModel->setSourceModel(probe->objectListModel()); - QAbstractProxyModel *proxy = new SingleColumnObjectProxyModel(this); - proxy->setSourceModel(windowModel); - m_windowModel = proxy; - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickWindowModel"), m_windowModel); - - auto filterProxy = new ServerProxyModel(this); - filterProxy->setSourceModel(m_itemModel); - filterProxy->addRole(ObjectModel::ObjectIdRole); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickItemModel"), filterProxy); - - if (m_probe->needsObjectDiscovery()) { - connect(m_probe, &Probe::objectCreated, this, &QuickInspector::objectCreated); - } - - connect(probe, &Probe::objectCreated, m_itemModel, &QuickItemModel::objectAdded); - connect(probe, &Probe::objectDestroyed, m_itemModel, &QuickItemModel::objectRemoved); - connect(probe, &Probe::objectFavorited, m_itemModel, &QuickItemModel::objectFavorited); - connect(probe, &Probe::objectUnfavorited, m_itemModel, &QuickItemModel::objectUnfavorited); - connect(probe, &Probe::objectSelected, this, &QuickInspector::qObjectSelected); - connect(probe, &Probe::nonQObjectSelected, this, &QuickInspector::nonQObjectSelected); - - m_itemSelectionModel = ObjectBroker::selectionModel(filterProxy); - connect(m_itemSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::itemSelectionChanged); - - auto sgFilterProxy = new ServerProxyModel(this); - sgFilterProxy->setSourceModel(m_sgModel); - probe->registerModel(QStringLiteral("com.kdab.GammaRay.QuickSceneGraphModel"), sgFilterProxy); - - m_sgSelectionModel = ObjectBroker::selectionModel(sgFilterProxy); - connect(m_sgSelectionModel, &QItemSelectionModel::selectionChanged, - this, &QuickInspector::sgSelectionChanged); - connect(m_sgModel, &QuickSceneGraphModel::nodeDeleted, this, &QuickInspector::sgNodeDeleted); - - connect(m_remoteView, &RemoteViewServer::elementsAtRequested, this, &QuickInspector::requestElementsAt); - connect(this, &QuickInspector::elementsAtReceived, m_remoteView, &RemoteViewServer::elementsAtReceived); - connect(m_remoteView, &RemoteViewServer::doPickElementId, this, &QuickInspector::pickElementId); - connect(m_remoteView, &RemoteViewServer::requestUpdate, this, &QuickInspector::slotGrabWindow); - connect(m_pendingRenderMode, &RenderModeRequest::aboutToCleanSceneGraph, this, &QuickInspector::aboutToCleanSceneGraph); - connect(m_pendingRenderMode, &RenderModeRequest::sceneGraphCleanedUp, this, &QuickInspector::sceneGraphCleanedUp); - -#ifndef QT_NO_OPENGL - auto texGrab = new QSGTextureGrabber(this); - connect(probe, &Probe::objectCreated, texGrab, &QSGTextureGrabber::objectCreated); -#endif - - connect(Endpoint::instance(), &Endpoint::disconnected, this, [this]() { - if (m_overlay) - m_overlay->placeOn(ItemOrLayoutFacade()); - }); - - ProblemCollector::registerProblemChecker("com.kdab.GammaRay.QuickItemChecker", - "QtQuick Item check", - "Warns about items that are visible but out of view.", - &QuickInspector::scanForProblems); - - // needs to be last, extensions require some of the above to be set up correctly - registerPCExtensions(); -} - -QuickInspector::~QuickInspector() -{ - if (m_overlay) { - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - } -} - -void QuickInspector::selectWindow(int index) -{ - const QModelIndex mi = m_windowModel->index(index, 0); - QQuickWindow *window = mi.data(ObjectModel::ObjectRole).value(); - selectWindow(window); -} - -void QuickInspector::selectWindow(QQuickWindow *window) -{ - if (m_window == window) { - return; - } - - if (m_window) { - const QByteArray mode = QQuickWindowPrivate::get(m_window)->visualizationMode; - - if (!mode.isEmpty()) { - auto reset = new RenderModeRequest(m_window); - connect(reset, &RenderModeRequest::finished, reset, &RenderModeRequest::deleteLater); - reset->applyOrDelay(m_window, QuickInspectorInterface::NormalRendering); - } - } - - m_window = window; - m_itemModel->setWindow(window); - m_sgModel->setWindow(window); - m_remoteView->setEventReceiver(m_window); - m_remoteView->resetView(); - recreateOverlay(); - - if (m_window) { - // make sure we have selected something for the property editor to not be entirely empty - selectItem(m_window->contentItem()); - m_window->update(); - } - - checkFeatures(); - - if (m_window) - setCustomRenderMode(m_renderMode); -} - -void QuickInspector::selectItem(QQuickItem *item) -{ - const QAbstractItemModel *model = m_itemSelectionModel->model(); - Model::used(model); - Model::used(m_sgSelectionModel->model()); - - const QModelIndexList indexList = model->match(model->index(0, 0), - ObjectModel::ObjectRole, - QVariant::fromValue(item), 1, - Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_itemSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::selectSGNode(QSGNode *node) -{ - const QAbstractItemModel *model = m_sgSelectionModel->model(); - Model::used(model); - - const QModelIndexList indexList = model->match(model->index(0, 0), ObjectModel::ObjectRole, - QVariant::fromValue( - node), - 1, - Qt::MatchExactly | Qt::MatchRecursive - | Qt::MatchWrap); - if (indexList.isEmpty()) - return; - - const QModelIndex index = indexList.first(); - m_sgSelectionModel->select(index, - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); -} - -void QuickInspector::qObjectSelected(QObject *object) -{ - if (auto item = qobject_cast(object)) - selectItem(item); - else if (auto window = qobject_cast(object)) - selectWindow(window); -} - -void QuickInspector::nonQObjectSelected(void *object, const QString &typeName) -{ - auto metaObject = MetaObjectRepository::instance()->metaObject(typeName); - if (metaObject && metaObject->inherits(QStringLiteral("QSGNode"))) - selectSGNode(reinterpret_cast(object)); -} - -void QuickInspector::objectCreated(QObject *object) -{ - if (QQuickWindow *window = qobject_cast(object)) { - if (QQuickView *view = qobject_cast(object)) { - if (view->engine()) { - m_probe->discoverObject(view->engine()); - } - } else { - QQmlContext *context = QQmlEngine::contextForObject(window); - QQmlEngine *engine = context ? context->engine() : nullptr; - - if (!engine) { - // engine = qmlEngine(window->contentItem()->childItems().value(0)); - QQuickItem *contentItem = window->contentItem(); - if (contentItem) { - const auto children = contentItem->childItems(); - if (!children.isEmpty()) { - engine = qmlEngine(children.first()); - } - } - } - - if (engine) { - m_probe->discoverObject(engine); - } - } - } -} - -void QuickInspector::recreateOverlay() -{ - ProbeGuard guard; - if (m_overlay) - disconnect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); - - m_overlay = AbstractScreenGrabber::get(m_window); - if (!m_overlay) - return; - - connect(m_overlay.get(), &AbstractScreenGrabber::grabberReadyChanged, m_remoteView, &RemoteViewServer::setGrabberReady); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneChanged, m_remoteView, &RemoteViewServer::sourceChanged); - connect(m_overlay.get(), &AbstractScreenGrabber::sceneGrabbed, this, &QuickInspector::sendRenderedScene); - // the target application might have destroyed the overlay widget - // (e.g. because the parent of the overlay got destroyed). - // just recreate a new one in this case - connect(m_overlay.get(), &QObject::destroyed, this, &QuickInspector::recreateOverlay); // FIXME Is it really needed? - // It is for the widget inspector, but for qt quick? - connect(this, &QuickInspectorInterface::serverSideDecorationChanged, m_overlay.get(), &AbstractScreenGrabber::setDecorationsEnabled); - m_overlay->setDecorationsEnabled(serverSideDecorationEnabled()); - - m_remoteView->setGrabberReady(true); -} - -void QuickInspector::aboutToCleanSceneGraph() -{ - m_sgModel->setWindow(nullptr); - m_currentSgNode = nullptr; - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::sceneGraphCleanedUp() -{ - m_sgModel->setWindow(m_window); -} - -void QuickInspector::sendRenderedScene(const GammaRay::GrabbedFrame &grabbedFrame) -{ - if (!m_window) - return; - RemoteViewFrame frame; - frame.setImage(grabbedFrame.image, grabbedFrame.transform); - frame.setSceneRect(grabbedFrame.itemsGeometryRect); - frame.setViewRect(QRect(0, 0, m_window->width(), m_window->height())); - if (m_overlay && m_overlay->settings().componentsTraces) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry); - else if (!grabbedFrame.itemsGeometry.isEmpty()) - frame.data = QVariant::fromValue(grabbedFrame.itemsGeometry.at(0)); - m_remoteView->sendFrame(frame); -} - -void QuickInspector::slotGrabWindow() -{ - if (!m_remoteView->isActive() || !m_window) - return; - - Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread()); - if (m_overlay) { - m_overlay->requestGrabWindow(m_remoteView->userViewport()); - } -} - -void QuickInspector::setCustomRenderMode( - GammaRay::QuickInspectorInterface::RenderMode customRenderMode) -{ - - m_renderMode = customRenderMode; - - m_pendingRenderMode->applyOrDelay(m_window, customRenderMode); - - const bool tracing = customRenderMode == QuickInspectorInterface::VisualizeTraces; - if (m_overlay && m_overlay->settings().componentsTraces != tracing) { - auto settings = m_overlay->settings(); - settings.componentsTraces = tracing; - setOverlaySettings(settings); - } -} - -void QuickInspector::checkFeatures() -{ - Features f; - if (!m_window) { - emit features(f); - return; - } - - if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) - f = AllCustomRenderModes; - else if (m_window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) - f = AnalyzePainting; - - emit features(f); -} - -void QuickInspector::setOverlaySettings(const GammaRay::QuickDecorationsSettings &settings) -{ - if (!m_overlay) { - emit overlaySettings(QuickDecorationsSettings()); // Let's not leave the client without an answer. - return; - } - - m_overlay->setSettings(settings); - emit overlaySettings(m_overlay->settings()); -} - -void QuickInspector::checkOverlaySettings() -{ - emit overlaySettings(m_overlay ? m_overlay->settings() : QuickDecorationsSettings()); -} - -class SGSoftwareRendererPrivacyViolater : public QSGAbstractSoftwareRenderer -{ -public: - using QSGAbstractSoftwareRenderer::buildRenderList; - using QSGAbstractSoftwareRenderer::optimizeRenderList; - using QSGAbstractSoftwareRenderer::renderNodes; -}; - -#if defined(Q_CC_CLANG) || defined(Q_CC_GNU) -// keep it working in UBSAN -__attribute__((no_sanitize("vptr"))) -#endif -void QuickInspector::analyzePainting() -{ - if (!m_window || m_window->rendererInterface()->graphicsApi() != QSGRendererInterface::Software || !PaintAnalyzer::isAvailable()) - return; - - m_paintAnalyzer->beginAnalyzePainting(); - m_paintAnalyzer->setBoundingRect(QRect(QPoint(), m_window->size())); - { - auto w = QQuickWindowPrivate::get(m_window); - auto renderer = static_cast(w->renderer); - - // this replicates what QSGSoftwareRender is doing - QPainter painter(m_paintAnalyzer->paintDevice()); - painter.setRenderHint(QPainter::Antialiasing); - auto rc = static_cast(w->renderer->context()); - auto prevPainter = rc->m_activePainter; - rc->m_activePainter = &painter; - renderer->markDirty(); - renderer->buildRenderList(); - renderer->optimizeRenderList(); - renderer->renderNodes(&painter); - - rc->m_activePainter = prevPainter; - } - m_paintAnalyzer->endAnalyzePainting(); -} - -void QuickInspector::checkSlowMode() -{ - // We can't check that for now as there is no getter for the property... - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::setSlowMode(bool slow) -{ - if (m_slowDownEnabled == slow) - return; - - static QHash connections; - - m_slowDownEnabled = slow; - - for (int i = 0; i < m_windowModel->rowCount(); ++i) { - const QModelIndex index = m_windowModel->index(i, 0); - QQuickWindow *window = index.data(ObjectModel::ObjectRole).value(); - auto it = connections.find(window); - - if (it == connections.end()) { - connections.insert(window, connect(window, &QQuickWindow::beforeRendering, this, [this, window]() { - auto it = connections.find(window); -#if QT_VERSION >= QT_VERSION_CHECK(6, 11, 0) - QUnifiedTimer::instance()->setSpeedModifier(m_slowDownEnabled ? (1. / 5.) : 1.); -#else - QUnifiedTimer::instance()->setSlowModeEnabled(m_slowDownEnabled); -#endif - QObject::disconnect(it.value()); - connections.erase(it); }, Qt::DirectConnection)); - } - } - - emit slowModeChanged(m_slowDownEnabled); -} - -void QuickInspector::itemSelectionChanged(const QItemSelection &selection) -{ - const QModelIndex index = selection.value(0).topLeft(); - m_currentItem = index.data(ObjectModel::ObjectRole).value(); - m_itemPropertyController->setObject(m_currentItem); - - // It might be that a sg-node is already selected that belongs to this item, but isn't the root - // node of the Item. In this case we don't want to overwrite that selection. - if (m_sgModel->itemForSgNode(m_currentSgNode) != m_currentItem) { - m_currentSgNode = m_sgModel->sgNodeForItem(m_currentItem); - const auto sourceIdx = m_sgModel->indexForNode(m_currentSgNode); - auto proxy = qobject_cast(m_sgSelectionModel->model()); - m_sgSelectionModel->select(proxy->mapFromSource(sourceIdx), - QItemSelectionModel::Select - | QItemSelectionModel::Clear - | QItemSelectionModel::Rows - | QItemSelectionModel::Current); - } - - if (m_overlay) - m_overlay->placeOn(m_currentItem.data()); -} - -void QuickInspector::sgSelectionChanged(const QItemSelection &selection) -{ - if (selection.isEmpty()) - return; - - const QModelIndex index = selection.first().topLeft(); - m_currentSgNode = index.data(ObjectModel::ObjectRole).value(); - if (!m_sgModel->verifyNodeValidity(m_currentSgNode)) - return; // Apparently the node has been deleted meanwhile, so don't access it. - - void *obj = m_currentSgNode; - auto mo = MetaObjectRepository::instance()->metaObject(QStringLiteral("QSGNode"), obj); - m_sgPropertyController->setObject(m_currentSgNode, mo->className()); - - m_currentItem = m_sgModel->itemForSgNode(m_currentSgNode); - selectItem(m_currentItem); -} - -void QuickInspector::sgNodeDeleted(QSGNode *node) -{ - if (m_currentSgNode == node) - m_sgPropertyController->setObject(nullptr, QString()); -} - -void QuickInspector::requestElementsAt(const QPoint &pos, GammaRay::RemoteViewInterface::RequestMode mode) -{ - if (!m_window) - return; - - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(m_window->contentItem(), pos, mode, bestCandidate); - - if (!objects.isEmpty()) { - emit elementsAtReceived(objects, bestCandidate); - } -} - -void QuickInspector::pickElementId(const GammaRay::ObjectId &id) -{ - QQuickItem *item = id.asQObjectType(); - if (item) - m_probe->selectObject(item); -} - -QRectF QuickInspector::combinedChildrenRect(QQuickItem *parent) const -{ - auto rect = parent->childrenRect(); - - const auto childItems = parent->childItems(); - for (const auto child : childItems) { - auto childRect = child->childrenRect(); - - // Get Global positon of childRect - QPointF childGlobalPos = child->mapToScene(QPointF(0, 0)); - - // Convert global position to local coordinates of the parent object - QPointF localChildPos = parent->mapFromScene(childGlobalPos); - - // Adjust childRect to be in local coordinates of the parent object - childRect.moveTopLeft(localChildPos.toPoint()); - - // Adding the childRect to the rect - rect = rect.united(childRect); - } - - return rect; -} - -ObjectIds QuickInspector::recursiveItemsAt(QQuickItem *parent, const QPointF &pos, - GammaRay::RemoteViewInterface::RequestMode mode, - int &bestCandidate, bool parentIsGoodCandidate) const -{ - Q_ASSERT(parent); - ObjectIds objects; - - bestCandidate = -1; - if (parentIsGoodCandidate) { - // inherit the parent item opacity when looking for a good candidate item - // i.e. QQuickItem::isVisible is taking the parent into account already, but - // the opacity doesn't - we have to do this manually - // Yet we have to ignore ItemHasContents apparently, as the QQuickRootItem - // at least seems to not have this flag set. - parentIsGoodCandidate = isGoodCandidateItem(parent, true); - } - - auto childItems = parent->childItems(); - std::stable_sort(childItems.begin(), childItems.end(), - [](QQuickItem *lhs, QQuickItem *rhs) { return lhs->z() < rhs->z(); }); - - for (int i = childItems.size() - 1; i >= 0; --i) { // backwards to match z order - const auto child = childItems.at(i); - const auto requestedPoint = parent->mapToItem(child, pos); - if (!child->childItems().isEmpty() && (child->contains(requestedPoint) || combinedChildrenRect(child).contains(requestedPoint))) { - const int count = objects.size(); - int bc; // possibly better candidate among subChildren - objects << recursiveItemsAt(child, requestedPoint, mode, bc, parentIsGoodCandidate); - - if (bestCandidate == -1 && parentIsGoodCandidate && bc != -1) { - bestCandidate = count + bc; - } - } - - if (child->contains(requestedPoint)) { - if (bestCandidate == -1 && parentIsGoodCandidate && isGoodCandidateItem(child)) { - bestCandidate = objects.size(); - } - objects << ObjectId(child); - } - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - break; - } - } - - if (bestCandidate == -1 && parentIsGoodCandidate && itemHasContents(parent)) { - bestCandidate = objects.size(); - } - - objects << ObjectId(parent); - - if (bestCandidate != -1 && mode == RemoteViewInterface::RequestBest) { - objects = ObjectIds() << objects[bestCandidate]; - bestCandidate = 0; - } - - return objects; -} - - -void QuickInspector::scanForProblems() -{ - const QVector &allObjects = Probe::instance()->allQObjects(); - - QMutexLocker lock(Probe::objectLock()); - for (QObject *obj : allObjects) { - QQuickItem *item; - if (!Probe::instance()->isValidObject(obj) || !(item = qobject_cast(obj))) - continue; - - QQuickItem *ancestor = item->parentItem(); - auto rect = item->mapRectToScene(QRectF(0, 0, item->width(), item->height())); - - while (ancestor && item->window() && ancestor != item->window()->contentItem()) { - if (ancestor->parentItem() == item->window()->contentItem() || ancestor->clip()) { - auto ancestorRect = ancestor->mapRectToScene(QRectF(0, 0, ancestor->width(), ancestor->height())); - - if (!ancestorRect.contains(rect) && !rect.intersects(ancestorRect)) { - Problem p; - p.severity = Problem::Info; - p.description = QStringLiteral("QtQuick: %1 %2 (0x%3) is visible, but out of view.").arg(ObjectDataProvider::typeName(item), ObjectDataProvider::name(item), QString::number(reinterpret_cast(item), 16)); - p.object = ObjectId(item); - p.locations.push_back(ObjectDataProvider::creationLocation(item)); - p.problemId = QStringLiteral("com.kdab.GammaRay.QuickItemChecker.OutOfView:%1").arg(reinterpret_cast(item)); - p.findingCategory = Problem::Scan; - ProblemCollector::addProblem(p); - break; - } - } - ancestor = ancestor->parentItem(); - } - } -} - -bool QuickInspector::eventFilter(QObject *receiver, QEvent *event) -{ - if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEv = static_cast(event); - if (mouseEv->button() == Qt::LeftButton && mouseEv->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { - QQuickWindow *window = qobject_cast(receiver); - if (window && window->contentItem()) { - int bestCandidate; - const ObjectIds objects = recursiveItemsAt(window->contentItem(), mouseEv->pos(), - RemoteViewInterface::RequestBest, bestCandidate); - m_probe->selectObject(objects.value(bestCandidate == -1 ? 0 : bestCandidate).asQObject()); - } - } - } - - return QObject::eventFilter(receiver, event); -} - -void QuickInspector::registerMetaTypes() -{ - MetaObject *mo = nullptr; - MO_ADD_METAOBJECT1(QQuickWindow, QWindow); - - MO_ADD_PROPERTY(QQuickWindow, renderTarget, setRenderTarget); - MO_ADD_PROPERTY(QQuickWindow, graphicsConfiguration, setGraphicsConfiguration); - MO_ADD_PROPERTY(QQuickWindow, graphicsDevice, setGraphicsDevice); - -#ifndef QT_NO_OPENGL - MO_ADD_PROPERTY(QQuickWindow, isPersistentGraphics, setPersistentGraphics); -#endif - - MO_ADD_PROPERTY_RO(QQuickWindow, mouseGrabberItem); - MO_ADD_PROPERTY(QQuickWindow, isPersistentSceneGraph, setPersistentSceneGraph); - MO_ADD_PROPERTY_RO(QQuickWindow, effectiveDevicePixelRatio); - - MO_ADD_PROPERTY_RO(QQuickWindow, rendererInterface); - - MO_ADD_METAOBJECT0(QSGRendererInterface); - MO_ADD_PROPERTY_RO(QSGRendererInterface, graphicsApi); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderCompilationType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderSourceType); - MO_ADD_PROPERTY_RO(QSGRendererInterface, shaderType); - - MO_ADD_METAOBJECT1(QQuickView, QQuickWindow); - MO_ADD_PROPERTY_RO(QQuickView, engine); - MO_ADD_PROPERTY_RO(QQuickView, errors); - MO_ADD_PROPERTY_RO(QQuickView, initialSize); - MO_ADD_PROPERTY_RO(QQuickView, rootContext); - MO_ADD_PROPERTY_RO(QQuickView, rootObject); - - MO_ADD_METAOBJECT1(QQuickItem, QObject); - MO_ADD_PROPERTY(QQuickItem, acceptHoverEvents, setAcceptHoverEvents); - MO_ADD_PROPERTY(QQuickItem, acceptedMouseButtons, setAcceptedMouseButtons); - MO_ADD_PROPERTY(QQuickItem, cursor, setCursor); - MO_ADD_PROPERTY(QQuickItem, filtersChildMouseEvents, setFiltersChildMouseEvents); - MO_ADD_PROPERTY(QQuickItem, flags, setFlags); - MO_ADD_PROPERTY_RO(QQuickItem, isFocusScope); - MO_ADD_PROPERTY_RO(QQuickItem, isTextureProvider); - MO_ADD_PROPERTY(QQuickItem, keepMouseGrab, setKeepMouseGrab); - MO_ADD_PROPERTY(QQuickItem, keepTouchGrab, setKeepTouchGrab); - MO_ADD_PROPERTY_LD(QQuickItem, nextItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain() : nullptr; }); - MO_ADD_PROPERTY_LD(QQuickItem, previousItemInFocusChain, [](QQuickItem *item) { return item->isVisible() ? item->nextItemInFocusChain(false) : nullptr; }); - MO_ADD_PROPERTY_RO(QQuickItem, scopedFocusItem); - MO_ADD_PROPERTY_RO(QQuickItem, window); - - MO_ADD_METAOBJECT1(QQuickPaintedItem, QQuickItem); - MO_ADD_PROPERTY_RO(QQuickPaintedItem, contentsBoundingRect); - MO_ADD_PROPERTY(QQuickPaintedItem, mipmap, setMipmap); - MO_ADD_PROPERTY(QQuickPaintedItem, opaquePainting, setOpaquePainting); - MO_ADD_PROPERTY(QQuickPaintedItem, performanceHints, setPerformanceHints); - - MO_ADD_METAOBJECT1(QSGTexture, QObject); - MO_ADD_PROPERTY(QSGTexture, anisotropyLevel, setAnisotropyLevel); - MO_ADD_PROPERTY(QSGTexture, filtering, setFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, hasAlphaChannel); - MO_ADD_PROPERTY_RO(QSGTexture, hasMipmaps); - MO_ADD_PROPERTY(QSGTexture, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY_RO(QSGTexture, isAtlasTexture); - MO_ADD_PROPERTY(QSGTexture, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY_RO(QSGTexture, normalizedTextureSubRect); - // crashes without a current GL context - // MO_ADD_PROPERTY_RO(QSGTexture, textureId); - MO_ADD_PROPERTY_RO(QSGTexture, textureSize); - MO_ADD_PROPERTY(QSGTexture, verticalWrapMode, setVerticalWrapMode); - - MO_ADD_METAOBJECT0(QSGNode); - MO_ADD_PROPERTY_RO(QSGNode, parent); - MO_ADD_PROPERTY_RO(QSGNode, childCount); - MO_ADD_PROPERTY_RO(QSGNode, flags); - MO_ADD_PROPERTY_RO(QSGNode, isSubtreeBlocked); -#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) - MO_ADD_PROPERTY(QSGNode, dirtyState, markDirty); // NOLINT -#endif - - MO_ADD_METAOBJECT1(QSGBasicGeometryNode, QSGNode); - MO_ADD_PROPERTY_O1(QSGBasicGeometryNode, geometry); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, matrix); - MO_ADD_PROPERTY_RO(QSGBasicGeometryNode, clipList); - - MO_ADD_METAOBJECT1(QSGGeometryNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGGeometryNode, material, setMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, opaqueMaterial, setOpaqueMaterial); - MO_ADD_PROPERTY_RO(QSGGeometryNode, activeMaterial); - MO_ADD_PROPERTY(QSGGeometryNode, renderOrder, setRenderOrder); - MO_ADD_PROPERTY(QSGGeometryNode, inheritedOpacity, setInheritedOpacity); - - MO_ADD_METAOBJECT1(QSGClipNode, QSGBasicGeometryNode); - MO_ADD_PROPERTY(QSGClipNode, isRectangular, setIsRectangular); - MO_ADD_PROPERTY(QSGClipNode, clipRect, setClipRect); - - MO_ADD_METAOBJECT1(QSGTransformNode, QSGNode); - MO_ADD_PROPERTY(QSGTransformNode, matrix, setMatrix); - MO_ADD_PROPERTY(QSGTransformNode, combinedMatrix, setCombinedMatrix); - - MO_ADD_METAOBJECT1(QSGRootNode, QSGNode); - - MO_ADD_METAOBJECT1(QSGOpacityNode, QSGNode); - MO_ADD_PROPERTY(QSGOpacityNode, opacity, setOpacity); - MO_ADD_PROPERTY(QSGOpacityNode, combinedOpacity, setCombinedOpacity); - - MO_ADD_METAOBJECT1(QSGRenderNode, QSGNode); - MO_ADD_PROPERTY_RO(QSGRenderNode, changedStates); - MO_ADD_PROPERTY_RO(QSGRenderNode, flags); - MO_ADD_PROPERTY_RO(QSGRenderNode, rect); - MO_ADD_PROPERTY_RO(QSGRenderNode, inheritedOpacity); - MO_ADD_PROPERTY_RO(QSGRenderNode, matrix); - MO_ADD_PROPERTY_RO(QSGRenderNode, clipList); - - MO_ADD_METAOBJECT0(QSGMaterial); - MO_ADD_PROPERTY_RO(QSGMaterial, flags); - - MO_ADD_METAOBJECT1(QSGFlatColorMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGFlatColorMaterial, color, setColor); - - MO_ADD_METAOBJECT1(QSGOpaqueTextureMaterial, QSGMaterial); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, filtering, setFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, horizontalWrapMode, setHorizontalWrapMode); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, mipmapFiltering, setMipmapFiltering); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, texture, setTexture); - MO_ADD_PROPERTY(QSGOpaqueTextureMaterial, verticalWrapMode, setVerticalWrapMode); - MO_ADD_METAOBJECT1(QSGTextureMaterial, QSGOpaqueTextureMaterial); - - MO_ADD_METAOBJECT1(QSGVertexColorMaterial, QSGMaterial); - -#ifndef QT_NO_OPENGL - MO_ADD_METAOBJECT1(QSGDistanceFieldTextMaterial, QSGMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, color); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, fontScale); - MO_ADD_PROPERTY_RO(QSGDistanceFieldTextMaterial, textureSize); - - MO_ADD_METAOBJECT1(QSGDistanceFieldStyledTextMaterial, QSGDistanceFieldTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldStyledTextMaterial, styleColor); - - MO_ADD_METAOBJECT1(QSGDistanceFieldShiftedStyleTextMaterial, QSGDistanceFieldStyledTextMaterial); - MO_ADD_PROPERTY_RO(QSGDistanceFieldShiftedStyleTextMaterial, shift); -#endif -} - -#define E(x) \ - { \ - QSGRendererInterface::x, #x \ - } -static const MetaEnum::Value qsg_graphics_api_table[] = { - E(Unknown), - E(Software), - E(OpenGL), - E(OpenVG), - E(Direct3D11), - E(Vulkan), - E(Metal), -}; - -static const MetaEnum::Value qsg_shader_compilation_type_table[] = { - E(RuntimeCompilation), - E(OfflineCompilation) -}; - -static const MetaEnum::Value qsg_shader_source_type_table[] = { - E(ShaderSourceString), - E(ShaderSourceFile), - E(ShaderByteCode) -}; - -static const MetaEnum::Value qsg_shader_type_table[] = { - E(UnknownShadingLanguage), - E(GLSL), - E(HLSL) -}; -#undef E - -#define E(x) \ - { \ - QSGRenderNode::x, #x \ - } -static const MetaEnum::Value render_node_state_flags_table[] = { - E(DepthState), - E(StencilState), - E(ScissorState), - E(ColorState), - E(BlendState), - E(CullState), - E(CullState), - E(ViewportState), - E(RenderTargetState) -}; - -static const MetaEnum::Value render_node_rendering_flags_table[] = { - E(BoundedRectRendering), - E(DepthAwareRendering), - E(OpaqueRendering) -}; -#undef E - -static QString anchorLineToString(const QQuickAnchorLine &line) -{ - if (!line.item - || line.anchorLine == QQuickAnchors::InvalidAnchor) { - return QStringLiteral(""); - } - QString s = Util::shortDisplayString(line.item); - switch (line.anchorLine) { - case QQuickAnchors::LeftAnchor: - return s + QStringLiteral(".left"); - case QQuickAnchors::RightAnchor: - return s + QStringLiteral(".right"); - case QQuickAnchors::TopAnchor: - return s + QStringLiteral(".top"); - case QQuickAnchors::BottomAnchor: - return s + QStringLiteral(".bottom"); - case QQuickAnchors::HCenterAnchor: - return s + QStringLiteral(".horizontalCenter"); - case QQuickAnchors::VCenterAnchor: - return s + QStringLiteral(".verticalCenter"); - case QQuickAnchors::BaselineAnchor: - return s + QStringLiteral(".baseline"); - default: - break; - } - return s; -} - -void QuickInspector::registerVariantHandlers() -{ - ER_REGISTER_FLAGS(QQuickItem, Flags, qqitem_flag_table); - ER_REGISTER_FLAGS(QSGNode, DirtyState, qsg_node_dirtystate_table); - ER_REGISTER_FLAGS(QSGNode, Flags, qsg_node_flag_table); - ER_REGISTER_ENUM(QSGTexture, AnisotropyLevel, qsg_texture_anisotropy_table); - ER_REGISTER_ENUM(QSGTexture, Filtering, qsg_texture_filtering_table); - ER_REGISTER_ENUM(QSGTexture, WrapMode, qsg_texture_wrapmode_table); - - VariantHandler::registerStringConverter( - qQuickPaintedItemPerformanceHintsToString); - VariantHandler::registerStringConverter(anchorLineToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(qsgMaterialFlagsToString); - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_state_flags_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(render_node_rendering_flags_table)); - - VariantHandler::registerStringConverter(Util::addressToString); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_graphics_api_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_compilation_type_table)); - VariantHandler::registerStringConverter(MetaEnum::flagsToString_fn(qsg_shader_source_type_table)); - VariantHandler::registerStringConverter(MetaEnum::enumToString_fn(qsg_shader_type_table)); -} - -void QuickInspector::registerPCExtensions() -{ -#ifndef QT_NO_OPENGL - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - PropertyController::registerExtension(); - - PropertyAdaptorFactory::registerFactory(QQuickOpenGLShaderEffectMaterialAdaptorFactory::instance()); -#endif - PropertyAdaptorFactory::registerFactory(QuickAnchorsPropertyAdaptorFactory::instance()); - PropertyFilters::registerFilter(PropertyFilter("QQuickItem", "anchors")); - - BindingAggregator::registerBindingProvider(std::unique_ptr(new QuickImplicitBindingDependencyProvider)); -} diff --git a/.history/probe/hooks_20260413092956.cpp b/.history/probe/hooks_20260413092956.cpp deleted file mode 100644 index 09ed9d5f22..0000000000 --- a/.history/probe/hooks_20260413092956.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - hooks.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2012 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ -// krazy:excludeall=cpp due to low-level stuff in here - -#include - -#include "hooks.h" -#include "probecreator.h" - -#include - -#include - -#include - -#include //cannot use cstdio on QNX6.6 -#include - -#ifdef Q_OS_MAC -#include -#include -#include -#include -#elif defined(Q_OS_WIN) -#include -#endif - -#define IF_DEBUG(x) - -using namespace GammaRay; - -static void log_injection(const char *msg) -{ -#ifdef Q_OS_WIN - OutputDebugStringA(msg); -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-security" - printf(msg); // NOLINT clang-tidy -#pragma GCC diagnostic pop -#endif -} - -static void gammaray_pre_routine() -{ -#ifdef Q_OS_WIN - if (qApp) // DllMain will do a better job at this, we are too early here and might not even have our staticMetaObject properly resolved - return; -#endif - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) -Q_COREAPP_STARTUP_FUNCTION(gammaray_pre_routine) - -// previously installed Qt hooks, for daisy-chaining -static void (*gammaray_next_startup_hook)() = nullptr; -static void (*gammaray_next_addObject)(QObject *) = nullptr; -static void (*gammaray_next_removeObject)(QObject *) = nullptr; - -extern "C" Q_DECL_EXPORT void gammaray_startup_hook() -{ - Probe::startupHookReceived(); - new ProbeCreator(ProbeCreator::Create); - - if (gammaray_next_startup_hook) - gammaray_next_startup_hook(); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_addObject(QObject *obj) -{ - Probe::objectAdded(obj, true); - - if (gammaray_next_addObject) - gammaray_next_addObject(obj); -} - -extern "C" Q_DECL_EXPORT void gammaray_removeObject(QObject *obj) -{ - Probe::objectRemoved(obj); - - if (gammaray_next_removeObject) - gammaray_next_removeObject(obj); -} - -static void installQHooks() -{ - Q_ASSERT(qtHookData[QHooks::HookDataVersion] >= 1); - Q_ASSERT(qtHookData[QHooks::HookDataSize] >= 6); - - gammaray_next_addObject = reinterpret_cast(qtHookData[QHooks::AddQObject]); - gammaray_next_removeObject = reinterpret_cast(qtHookData[QHooks::RemoveQObject]); - gammaray_next_startup_hook = reinterpret_cast(qtHookData[QHooks::Startup]); - - qtHookData[QHooks::AddQObject] = reinterpret_cast(&gammaray_addObject); - qtHookData[QHooks::RemoveQObject] = reinterpret_cast(&gammaray_removeObject); - qtHookData[QHooks::Startup] = reinterpret_cast(&gammaray_startup_hook); -} - -bool Hooks::hooksInstalled() -{ - return qtHookData[QHooks::AddQObject] == reinterpret_cast(&gammaray_addObject); -} - -void Hooks::installHooks() -{ - if (hooksInstalled()) - return; - - installQHooks(); -} - -extern "C" Q_DECL_EXPORT void gammaray_probe_inject() -{ - if (!qApp) { - return; - } - Hooks::installHooks(); - log_injection("gammaray_probe_inject()\n"); - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_probe_attach() -{ - if (!qApp) { - return; - } - log_injection("gammaray_probe_attach()\n"); - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects | ProbeCreator::ResendServerAddress); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_install_hooks() -{ - Hooks::installHooks(); -} diff --git a/.history/probe/hooks_20260413093443.cpp b/.history/probe/hooks_20260413093443.cpp deleted file mode 100644 index 9ee83e19c0..0000000000 --- a/.history/probe/hooks_20260413093443.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - hooks.cpp - - This file is part of GammaRay, the Qt application inspection and manipulation tool. - - SPDX-FileCopyrightText: 2012 Klarälvdalens Datakonsult AB, a KDAB Group company - Author: Volker Krause - - SPDX-License-Identifier: GPL-2.0-or-later - - Contact KDAB at for commercial licensing options. -*/ -// krazy:excludeall=cpp due to low-level stuff in here - -#include - -#include "hooks.h" -#include "probecreator.h" - -#include - -#include - -#include - -#include //cannot use cstdio on QNX6.6 -#include - -#ifdef Q_OS_MAC -#include -#include -#include -#include -#elif defined(Q_OS_WIN) -#include -#endif - -#define IF_DEBUG(x) - -using namespace GammaRay; - -static void log_injection(const char *msg) -{ -#ifdef Q_OS_WIN - OutputDebugStringA(msg); -#else -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-security" - printf(msg); // NOLINT clang-tidy -#pragma GCC diagnostic pop -#endif -} - -static void gammaray_pre_routine() -{ -#ifdef Q_OS_WIN - if (qApp) // DllMain will do a better job at this, we are too early here and might not even have our staticMetaObject properly resolved - return; -#endif - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) -Q_COREAPP_STARTUP_FUNCTION(gammaray_pre_routine) - -// previously installed Qt hooks, for daisy-chaining -static void (*gammaray_next_startup_hook)() = nullptr; -static void (*gammaray_next_addObject)(QObject *) = nullptr; -static void (*gammaray_next_removeObject)(QObject *) = nullptr; - -extern "C" Q_DECL_EXPORT void gammaray_startup_hook() -{ - Probe::startupHookReceived(); - new ProbeCreator(ProbeCreator::Create); - - if (gammaray_next_startup_hook) - gammaray_next_startup_hook(); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_addObject(QObject *obj) -{ - Probe::objectAdded(obj, true); - - if (gammaray_next_addObject) - gammaray_next_addObject(obj); -} - -extern "C" Q_DECL_EXPORT void gammaray_removeObject(QObject *obj) -{ - Probe::objectRemoved(obj); - - if (gammaray_next_removeObject) - gammaray_next_removeObject(obj); -} - -static void installQHooks() -{ - Q_ASSERT(qtHookData[QHooks::HookDataVersion] >= 1); - Q_ASSERT(qtHookData[QHooks::HookDataSize] >= 6); - - gammaray_next_addObject = reinterpret_cast(qtHookData[QHooks::AddQObject]); - gammaray_next_removeObject = reinterpret_cast(qtHookData[QHooks::RemoveQObject]); - gammaray_next_startup_hook = reinterpret_cast(qtHookData[QHooks::Startup]); - - qtHookData[QHooks::AddQObject] = reinterpret_cast(&gammaray_addObject); - qtHookData[QHooks::RemoveQObject] = reinterpret_cast(&gammaray_removeObject); - qtHookData[QHooks::Startup] = reinterpret_cast(&gammaray_startup_hook); -} - -bool Hooks::hooksInstalled() -{ - return qtHookData[QHooks::AddQObject] == reinterpret_cast(&gammaray_addObject); -} - -void Hooks::installHooks() -{ - if (hooksInstalled()) - return; - - installQHooks(); -} - -extern "C" Q_DECL_EXPORT void gammaray_probe_inject() -{ - if (!qApp) { - return; - } - Hooks::installHooks(); - log_injection("gammaray_probe_inject()\n"); - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_probe_attach() -{ - if (!qApp) { - return; - } - - Hooks::installHooks(); - Probe::startupHookReceived(); - - log_injection("gammaray_probe_attach()\n"); - new ProbeCreator(ProbeCreator::Create | ProbeCreator::FindExistingObjects | ProbeCreator::ResendServerAddress); -} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) - -extern "C" Q_DECL_EXPORT void gammaray_install_hooks() -{ - Hooks::installHooks(); -} From d7603d3f7186948bdd5724485045fb9f98904939 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:12:17 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- core/probe.cpp | 6 ++++-- plugins/quickinspector/quickinspector.cpp | 2 +- probe/hooks.cpp | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/probe.cpp b/core/probe.cpp index 85bdda8160..0624d9b980 100644 --- a/core/probe.cpp +++ b/core/probe.cpp @@ -872,8 +872,10 @@ void Probe::discoverObject(QObject *object) const auto children = object->children(); for (QObject *child : children) { - if (!child) continue; - if (m_validObjects.contains(child)) continue; + if (!child) + continue; + if (m_validObjects.contains(child)) + continue; discoverObject(child); } } diff --git a/plugins/quickinspector/quickinspector.cpp b/plugins/quickinspector/quickinspector.cpp index 1c2dc6abd8..0c3a7dbbad 100644 --- a/plugins/quickinspector/quickinspector.cpp +++ b/plugins/quickinspector/quickinspector.cpp @@ -529,7 +529,7 @@ void QuickInspector::objectCreated(QObject *object) } } } - + if (engine) { m_probe->discoverObject(engine); } diff --git a/probe/hooks.cpp b/probe/hooks.cpp index 9ee83e19c0..9c7ff5132f 100644 --- a/probe/hooks.cpp +++ b/probe/hooks.cpp @@ -133,7 +133,7 @@ extern "C" Q_DECL_EXPORT void gammaray_probe_attach() if (!qApp) { return; } - + Hooks::installHooks(); Probe::startupHookReceived();