diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 707bc37c..882d2bae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) add_subdirectory(build) add_subdirectory(launch) add_subdirectory(core) +add_subdirectory(debug) add_subdirectory(ipc) add_subdirectory(window) add_subdirectory(io) diff --git a/src/debug/CMakeLists.txt b/src/debug/CMakeLists.txt new file mode 100644 index 00000000..55da4fcb --- /dev/null +++ b/src/debug/CMakeLists.txt @@ -0,0 +1,7 @@ +qt_add_library(quickshell-debug STATIC + lint.cpp +) + +qs_pch(quickshell-debug) +target_link_libraries(quickshell-debug PRIVATE Qt::Quick) +target_link_libraries(quickshell PRIVATE quickshell-debug) diff --git a/src/debug/lint.cpp b/src/debug/lint.cpp new file mode 100644 index 00000000..1ec0086f --- /dev/null +++ b/src/debug/lint.cpp @@ -0,0 +1,81 @@ +#include "lint.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qs::debug { + +Q_LOGGING_CATEGORY(logLint, "quickshell.linter", QtWarningMsg); + +void lintZeroSized(QQuickItem* item); +bool isRenderable(QQuickItem* item); + +void lintObjectTree(QObject* object) { + if (!logLint().isWarningEnabled()) return; + + qCDebug(logLint) << "Walking children of object" << object; + + for (auto* child: object->children()) { + if (child->isQuickItemType()) { + auto* item = static_cast(child); // NOLINT; + lintItemTree(item); + } else { + lintObjectTree(child); + } + } +} + +void lintItemTree(QQuickItem* item) { + if (!logLint().isWarningEnabled()) return; + + qCDebug(logLint) << "Running lints for item" << item; + lintZeroSized(item); + + qCDebug(logLint) << "Walking visual children of item" << item; + for (auto* child: item->childItems()) { + lintItemTree(child); + } +} + +void lintZeroSized(QQuickItem* item) { + if (!item->isEnabled() || !item->isVisible()) return; + if (item->childItems().isEmpty()) return; + + auto zeroWidth = item->width() == 0; + auto zeroHeight = item->height() == 0; + + if (!zeroWidth && !zeroHeight) return; + + if (!isRenderable(item)) return; + + auto* ctx = QQmlEngine::contextForObject(item); + if (!ctx || ctx->baseUrl().scheme() != QStringLiteral("qsintercept")) return; + + qmlWarning(item) << "Item is visible and has visible children, but has zero " + << (zeroWidth && zeroHeight ? "width and height" + : zeroWidth ? "width" + : "height"); +} + +bool isRenderable(QQuickItem* item) { + if (!item->isEnabled() || !item->isVisible()) return false; + + if (item->flags().testFlags(QQuickItem::ItemHasContents)) { + return true; + } + + return std::ranges::any_of(item->childItems(), [](auto* item) { return isRenderable(item); }); +} + +} // namespace qs::debug diff --git a/src/debug/lint.hpp b/src/debug/lint.hpp new file mode 100644 index 00000000..5b5420ea --- /dev/null +++ b/src/debug/lint.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace qs::debug { + +void lintObjectTree(QObject* object); +void lintItemTree(QQuickItem* item); + +} // namespace qs::debug diff --git a/src/window/CMakeLists.txt b/src/window/CMakeLists.txt index 89b2233e..47b546d4 100644 --- a/src/window/CMakeLists.txt +++ b/src/window/CMakeLists.txt @@ -22,6 +22,8 @@ target_link_libraries(quickshell-window PRIVATE Qt::Core Qt::Gui Qt::Quick Qt6::QuickPrivate ) +qs_add_link_dependencies(quickshell-window quickshell-debug) + target_link_libraries(quickshell-window-init PRIVATE Qt::Qml) qs_module_pch(quickshell-window SET large) diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 3d01224d..426b4057 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -19,6 +19,7 @@ #include "../core/qmlscreen.hpp" #include "../core/region.hpp" #include "../core/reload.hpp" +#include "../debug/lint.hpp" #include "windowinterface.hpp" ProxyWindowBase::ProxyWindowBase(QObject* parent) @@ -214,6 +215,11 @@ void ProxyWindowBase::polishItems() { // This hack manually polishes the item tree right before showing the window so it will // always be created with the correct size. QQuickWindowPrivate::get(this->window)->polishItems(); + + if (!this->ranLints) { + qs::debug::lintItemTree(this->mContentItem); + this->ranLints = true; + } } qint32 ProxyWindowBase::x() const { diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 79d326e3..8ab8bfd0 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -130,6 +130,7 @@ protected: QQuickWindow* window = nullptr; QQuickItem* mContentItem = nullptr; bool reloadComplete = false; + bool ranLints = false; private: void polishItems();