diff --git a/CMakeLists.txt b/CMakeLists.txt index 4014cce2..f951d968 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ boption(SERVICE_UPOWER "UPower" ON) boption(SERVICE_NOTIFICATIONS "Notifications" ON) include(cmake/install-qml-module.cmake) +include(cmake/util.cmake) add_compile_options(-Wall -Wextra) @@ -87,9 +88,10 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug) endif() -set(QT_DEPS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Widgets) set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets) +include(cmake/pch.cmake) + if (BUILD_TESTING) enable_testing() add_definitions(-DQS_TEST) @@ -97,56 +99,27 @@ if (BUILD_TESTING) endif() if (SOCKETS) - list(APPEND QT_DEPS Qt6::Network) list(APPEND QT_FPDEPS Network) endif() if (WAYLAND) - list(APPEND QT_DEPS Qt6::WaylandClient Qt6::WaylandClientPrivate) list(APPEND QT_FPDEPS WaylandClient) endif() -if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS) +if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS) set(DBUS ON) endif() if (DBUS) - list(APPEND QT_DEPS Qt6::DBus) list(APPEND QT_FPDEPS DBus) endif() find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS}) +set(CMAKE_AUTOUIC OFF) qt_standard_project_setup(REQUIRES 6.6) set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules) -# pch breaks clang-tidy..... somehow -if (NOT NO_PCH) - file(GENERATE - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp - CONTENT "// intentionally empty" - ) - - add_library(qt-pch ${CMAKE_CURRENT_BINARY_DIR}/pchstub.cpp) - target_link_libraries(qt-pch PRIVATE ${QT_DEPS}) - target_precompile_headers(qt-pch PUBLIC - <memory> - <qobject.h> - <qqmlengine.h> - <qlist.h> - <qcolor.h> - <qquickitem.h> - <qevent.h> - ) -endif() - -function (qs_pch target) - if (NOT NO_PCH) - target_precompile_headers(${target} REUSE_FROM qt-pch) - target_link_libraries(${target} PRIVATE ${QT_DEPS}) # required for gcc to accept the pch on plugin targets - endif() -endfunction() - add_subdirectory(src) if (USE_JEMALLOC) diff --git a/cmake/pch.cmake b/cmake/pch.cmake new file mode 100644 index 00000000..e136015e --- /dev/null +++ b/cmake/pch.cmake @@ -0,0 +1,85 @@ +# pch breaks clang-tidy..... somehow +if (NOT NO_PCH) + file(GENERATE + OUTPUT ${CMAKE_BINARY_DIR}/pchstub.cpp + CONTENT "// intentionally empty" + ) +endif() + +function (qs_pch target) + if (NO_PCH) + return() + endif() + + cmake_parse_arguments(PARSE_ARGV 1 arg "" "SET" "") + + if ("${arg_SET}" STREQUAL "") + set(arg_SET "common") + endif() + + target_precompile_headers(${target} REUSE_FROM "qs-pchset-${arg_SET}") +endfunction() + +function (qs_module_pch target) + qs_pch(${target} ${ARGN}) + qs_pch("${target}plugin" SET plugin) + qs_pch("${target}plugin_init" SET plugin) +endfunction() + +function (qs_add_pchset SETNAME) + if (NO_PCH) + return() + endif() + + cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "HEADERS;DEPENDENCIES") + + set(LIBNAME "qs-pchset-${SETNAME}") + + add_library(${LIBNAME} ${CMAKE_BINARY_DIR}/pchstub.cpp) + target_link_libraries(${LIBNAME} ${arg_DEPENDENCIES}) + target_precompile_headers(${LIBNAME} PUBLIC ${arg_HEADERS}) +endfunction() + +set(COMMON_PCH_SET + <chrono> + <memory> + <vector> + <qdebug.h> + <qobject.h> + <qmetatype.h> + <qstring.h> + <qchar.h> + <qlist.h> + <qabstractitemmodel.h> +) + +qs_add_pchset(common + DEPENDENCIES Qt::Quick + HEADERS ${COMMON_PCH_SET} +) + +qs_add_pchset(large + DEPENDENCIES Qt::Quick + HEADERS + ${COMMON_PCH_SET} + <qiodevice.h> + <qevent.h> + <qcoreapplication.h> + <qqmlengine.h> + <qquickitem.h> + <qquickwindow.h> + <qcolor.h> + <qdir.h> + <qtimer.h> + <qabstractitemmodel.h> +) + + +# including qplugin.h directly will cause required symbols to disappear +qs_add_pchset(plugin + DEPENDENCIES Qt::Qml + HEADERS + <qobject.h> + <qjsonobject.h> + <qpointer.h> +) diff --git a/cmake/util.cmake b/cmake/util.cmake new file mode 100644 index 00000000..5d261a40 --- /dev/null +++ b/cmake/util.cmake @@ -0,0 +1,20 @@ +function (qs_append_qmldir target text) + get_property(qmldir_content TARGET ${target} PROPERTY _qt_internal_qmldir_content) + + if ("${qmldir_content}" STREQUAL "") + message(WARNING "qs_append_qmldir depends on private Qt cmake code, which has broken.") + return() + endif() + + set_property(TARGET ${target} APPEND_STRING PROPERTY _qt_internal_qmldir_content ${text}) +endfunction() + +# DEPENDENCIES introduces a cmake dependency which we don't need with static modules. +# This greatly improves comp speed by not introducing those dependencies. +function (qs_add_module_deps_light target) + foreach (dep IN LISTS ARGN) + string(APPEND qmldir_extra "depends ${dep}\n") + endforeach() + + qs_append_qmldir(${target} "${qmldir_extra}") +endfunction() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5b843543..c518a1c9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,8 @@ qt_add_executable(quickshell main.cpp) -target_link_libraries(quickshell PRIVATE ${QT_DEPS} quickshell-build) - install(TARGETS quickshell RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +add_subdirectory(launch) add_subdirectory(build) add_subdirectory(core) add_subdirectory(ipc) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 62f29425..c75dd588 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(CLI11 CONFIG REQUIRED) - qt_add_library(quickshell-core STATIC plugin.cpp shell.cpp @@ -37,10 +35,9 @@ qt_add_library(quickshell-core STATIC paths.cpp instanceinfo.cpp common.cpp + iconprovider.cpp ) -target_link_libraries(quickshell-core PRIVATE quickshell-build) - qt_add_qml_module(quickshell-core URI Quickshell VERSION 0.1 @@ -51,10 +48,9 @@ qt_add_qml_module(quickshell-core install_qml_module(quickshell-core) -target_link_libraries(quickshell-core PRIVATE ${QT_DEPS} CLI11::CLI11) +target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets) -qs_pch(quickshell-core) -qs_pch(quickshell-coreplugin) +qs_module_pch(quickshell-core SET large) target_link_libraries(quickshell PRIVATE quickshell-coreplugin) diff --git a/src/core/clock.cpp b/src/core/clock.cpp index 75785223..ebb7e92a 100644 --- a/src/core/clock.cpp +++ b/src/core/clock.cpp @@ -4,6 +4,7 @@ #include <qobject.h> #include <qtimer.h> #include <qtmetamacros.h> +#include <qtypes.h> #include "util.hpp" diff --git a/src/core/generation.cpp b/src/core/generation.cpp index 395f255b..147e2f93 100644 --- a/src/core/generation.cpp +++ b/src/core/generation.cpp @@ -8,17 +8,12 @@ #include <qfileinfo.h> #include <qfilesystemwatcher.h> #include <qhash.h> -#include <qicon.h> -#include <qiconengine.h> #include <qlogging.h> #include <qloggingcategory.h> #include <qobject.h> -#include <qpixmap.h> #include <qqmlcontext.h> #include <qqmlengine.h> #include <qqmlincubator.h> -#include <qquickimageprovider.h> -#include <qsize.h> #include <qtmetamacros.h> #include "iconimageprovider.hpp" @@ -331,90 +326,6 @@ EngineGeneration* EngineGeneration::currentGeneration() { } else return nullptr; } -// QMenu re-calls pixmap() every time the mouse moves so its important to cache it. -class PixmapCacheIconEngine: public QIconEngine { - void paint( - QPainter* /*unused*/, - const QRect& /*unused*/, - QIcon::Mode /*unused*/, - QIcon::State /*unused*/ - ) override { - qFatal( - ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; - } - - QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override { - if (this->lastPixmap.isNull() || size != this->lastSize) { - this->lastPixmap = this->createPixmap(size); - this->lastSize = size; - } - - return this->lastPixmap; - } - - virtual QPixmap createPixmap(const QSize& size) = 0; - -private: - QSize lastSize; - QPixmap lastPixmap; -}; - -class ImageProviderIconEngine: public PixmapCacheIconEngine { -public: - explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id) - : provider(provider) - , id(std::move(id)) {} - - QPixmap createPixmap(const QSize& size) override { - if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) { - return this->provider->requestPixmap(this->id, nullptr, size); - } else if (this->provider->imageType() == QQmlImageProviderBase::Image) { - auto image = this->provider->requestImage(this->id, nullptr, size); - return QPixmap::fromImage(image); - } else { - qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType(); - return QPixmap(); // never reached, satisfies lint - } - } - - [[nodiscard]] QIconEngine* clone() const override { - return new ImageProviderIconEngine(this->provider, this->id); - } - -private: - QQuickImageProvider* provider; - QString id; -}; - -QIcon EngineGeneration::iconByUrl(const QUrl& url) const { - if (url.isEmpty()) return QIcon(); - - auto scheme = url.scheme(); - if (scheme == "image") { - auto providerName = url.authority(); - auto path = url.path(); - if (!path.isEmpty()) path = path.sliced(1); - - auto* provider = qobject_cast<QQuickImageProvider*>(this->engine->imageProvider(providerName)); - - if (provider == nullptr) { - qWarning() << "iconByUrl failed: no provider found for" << url; - return QIcon(); - } - - if (provider->imageType() == QQmlImageProviderBase::Pixmap - || provider->imageType() == QQmlImageProviderBase::Image) - { - return QIcon(new ImageProviderIconEngine(provider, path)); - } - - } else { - qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url; - } - - return QIcon(); -} - EngineGeneration* EngineGeneration::findEngineGeneration(QQmlEngine* engine) { return g_generations.value(engine); } diff --git a/src/core/generation.hpp b/src/core/generation.hpp index 823ca82a..043d2f70 100644 --- a/src/core/generation.hpp +++ b/src/core/generation.hpp @@ -4,13 +4,11 @@ #include <qdir.h> #include <qfilesystemwatcher.h> #include <qhash.h> -#include <qicon.h> #include <qobject.h> #include <qpair.h> #include <qqmlengine.h> #include <qqmlincubator.h> #include <qtclasshelpermacros.h> -#include <qurl.h> #include "incubator.hpp" #include "qsintercept.hpp" @@ -54,8 +52,6 @@ public: // otherwise null. static EngineGeneration* currentGeneration(); - [[nodiscard]] QIcon iconByUrl(const QUrl& url) const; - RootWrapper* wrapper = nullptr; QDir rootPath; QmlScanner scanner; diff --git a/src/core/iconprovider.cpp b/src/core/iconprovider.cpp new file mode 100644 index 00000000..99b423ed --- /dev/null +++ b/src/core/iconprovider.cpp @@ -0,0 +1,105 @@ +#include "iconprovider.hpp" +#include <utility> + +#include <qicon.h> +#include <qiconengine.h> +#include <qlogging.h> +#include <qobject.h> +#include <qpixmap.h> +#include <qqmlengine.h> +#include <qquickimageprovider.h> +#include <qrect.h> +#include <qsize.h> +#include <qstring.h> + +#include "generation.hpp" + +// QMenu re-calls pixmap() every time the mouse moves so its important to cache it. +class PixmapCacheIconEngine: public QIconEngine { + void paint( + QPainter* /*unused*/, + const QRect& /*unused*/, + QIcon::Mode /*unused*/, + QIcon::State /*unused*/ + ) override { + qFatal( + ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; + } + + QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override { + if (this->lastPixmap.isNull() || size != this->lastSize) { + this->lastPixmap = this->createPixmap(size); + this->lastSize = size; + } + + return this->lastPixmap; + } + + virtual QPixmap createPixmap(const QSize& size) = 0; + +private: + QSize lastSize; + QPixmap lastPixmap; +}; + +class ImageProviderIconEngine: public PixmapCacheIconEngine { +public: + explicit ImageProviderIconEngine(QQuickImageProvider* provider, QString id) + : provider(provider) + , id(std::move(id)) {} + + QPixmap createPixmap(const QSize& size) override { + if (this->provider->imageType() == QQmlImageProviderBase::Pixmap) { + return this->provider->requestPixmap(this->id, nullptr, size); + } else if (this->provider->imageType() == QQmlImageProviderBase::Image) { + auto image = this->provider->requestImage(this->id, nullptr, size); + return QPixmap::fromImage(image); + } else { + qFatal() << "Unexpected ImageProviderIconEngine image type" << this->provider->imageType(); + return QPixmap(); // never reached, satisfies lint + } + } + + [[nodiscard]] QIconEngine* clone() const override { + return new ImageProviderIconEngine(this->provider, this->id); + } + +private: + QQuickImageProvider* provider; + QString id; +}; + +QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url) { + if (!engine || url.isEmpty()) return QIcon(); + + auto scheme = url.scheme(); + if (scheme == "image") { + auto providerName = url.authority(); + auto path = url.path(); + if (!path.isEmpty()) path = path.sliced(1); + + auto* provider = qobject_cast<QQuickImageProvider*>(engine->imageProvider(providerName)); + + if (provider == nullptr) { + qWarning() << "iconByUrl failed: no provider found for" << url; + return QIcon(); + } + + if (provider->imageType() == QQmlImageProviderBase::Pixmap + || provider->imageType() == QQmlImageProviderBase::Image) + { + return QIcon(new ImageProviderIconEngine(provider, path)); + } + + } else { + qWarning() << "iconByUrl failed: unsupported scheme" << scheme << "in path" << url; + } + + return QIcon(); +} + +QIcon getCurrentEngineImageAsIcon(const QUrl& url) { + auto* generation = EngineGeneration::currentGeneration(); + if (!generation) return QIcon(); + return getEngineImageAsIcon(generation->engine, url); +} diff --git a/src/core/iconprovider.hpp b/src/core/iconprovider.hpp new file mode 100644 index 00000000..173d20e6 --- /dev/null +++ b/src/core/iconprovider.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include <qicon.h> +#include <qqmlengine.h> +#include <qurl.h> + +QIcon getEngineImageAsIcon(QQmlEngine* engine, const QUrl& url); +QIcon getCurrentEngineImageAsIcon(const QUrl& url); diff --git a/src/core/platformmenu.cpp b/src/core/platformmenu.cpp index 09837ec9..a06575ea 100644 --- a/src/core/platformmenu.cpp +++ b/src/core/platformmenu.cpp @@ -19,7 +19,8 @@ #include "../window/proxywindow.hpp" #include "../window/windowinterface.hpp" -#include "generation.hpp" +#include "iconprovider.hpp" +#include "platformmenu_p.hpp" #include "popupanchor.hpp" #include "qsmenu.hpp" @@ -174,8 +175,7 @@ void PlatformMenuEntry::relayout() { auto icon = this->menu->icon(); if (!icon.isEmpty()) { - auto* generation = EngineGeneration::currentGeneration(); - this->qmenu->setIcon(generation->iconByUrl(this->menu->icon())); + this->qmenu->setIcon(getCurrentEngineImageAsIcon(icon)); } auto children = this->menu->children(); @@ -216,8 +216,7 @@ void PlatformMenuEntry::relayout() { auto icon = this->menu->icon(); if (!icon.isEmpty()) { - auto* generation = EngineGeneration::currentGeneration(); - this->qaction->setIcon(generation->iconByUrl(this->menu->icon())); + this->qaction->setIcon(getCurrentEngineImageAsIcon(icon)); } this->qaction->setEnabled(this->menu->enabled()); @@ -272,8 +271,7 @@ void PlatformMenuEntry::onIconChanged() { QIcon icon; if (!iconName.isEmpty()) { - auto* generation = EngineGeneration::currentGeneration(); - icon = generation->iconByUrl(iconName); + icon = getCurrentEngineImageAsIcon(iconName); } if (this->qmenu != nullptr) { diff --git a/src/core/platformmenu.hpp b/src/core/platformmenu.hpp index 5e8a0afe..5979f90e 100644 --- a/src/core/platformmenu.hpp +++ b/src/core/platformmenu.hpp @@ -5,9 +5,7 @@ #include <qaction.h> #include <qactiongroup.h> #include <qcontainerfwd.h> -#include <qmenu.h> #include <qobject.h> -#include <qpoint.h> #include <qqmlintegration.h> #include <qqmllist.h> #include <qtclasshelpermacros.h> @@ -18,17 +16,7 @@ namespace qs::menu::platform { -class PlatformMenuQMenu: public QMenu { -public: - explicit PlatformMenuQMenu() = default; - ~PlatformMenuQMenu() override; - Q_DISABLE_COPY_MOVE(PlatformMenuQMenu); - - void setVisible(bool visible) override; - - PlatformMenuQMenu* containingMenu = nullptr; - QPoint targetPosition; -}; +class PlatformMenuQMenu; class PlatformMenuEntry: public QObject { Q_OBJECT; diff --git a/src/core/platformmenu_p.hpp b/src/core/platformmenu_p.hpp new file mode 100644 index 00000000..9109959d --- /dev/null +++ b/src/core/platformmenu_p.hpp @@ -0,0 +1,19 @@ +#pragma once +#include <qmenu.h> +#include <qpoint.h> + +namespace qs::menu::platform { + +class PlatformMenuQMenu: public QMenu { +public: + explicit PlatformMenuQMenu() = default; + ~PlatformMenuQMenu() override; + Q_DISABLE_COPY_MOVE(PlatformMenuQMenu); + + void setVisible(bool visible) override; + + PlatformMenuQMenu* containingMenu = nullptr; + QPoint targetPosition; +}; + +} // namespace qs::menu::platform diff --git a/src/core/popupanchor.hpp b/src/core/popupanchor.hpp index 0897928a..a0f6353c 100644 --- a/src/core/popupanchor.hpp +++ b/src/core/popupanchor.hpp @@ -2,7 +2,6 @@ #include <optional> -#include <QtQmlIntegration/qqmlintegration.h> #include <qflags.h> #include <qnamespace.h> #include <qobject.h> diff --git a/src/core/test/CMakeLists.txt b/src/core/test/CMakeLists.txt index 448881a6..c9a82005 100644 --- a/src/core/test/CMakeLists.txt +++ b/src/core/test/CMakeLists.txt @@ -1,6 +1,6 @@ function (qs_test name) add_executable(${name} ${ARGN}) - target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test quickshell-core quickshell-window) + target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window) add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>) endfunction() diff --git a/src/dbus/CMakeLists.txt b/src/dbus/CMakeLists.txt index 49a4a06b..9948ea74 100644 --- a/src/dbus/CMakeLists.txt +++ b/src/dbus/CMakeLists.txt @@ -16,8 +16,18 @@ qt_add_library(quickshell-dbus STATIC # dbus headers target_include_directories(quickshell-dbus PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries(quickshell-dbus PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-dbus PRIVATE Qt::Core Qt::DBus) +# todo: link dbus to quickshell here instead of in modules that use it directly +# linker does not like this as is -qs_pch(quickshell-dbus) +qs_add_pchset(dbus + DEPENDENCIES Qt::DBus + HEADERS + <QtDBus> + <qdebug.h> + <qdbusargument.h> +) + +qs_pch(quickshell-dbus SET dbus) add_subdirectory(dbusmenu) diff --git a/src/dbus/dbusmenu/CMakeLists.txt b/src/dbus/dbusmenu/CMakeLists.txt index f9e4446c..ac50b28a 100644 --- a/src/dbus/dbusmenu/CMakeLists.txt +++ b/src/dbus/dbusmenu/CMakeLists.txt @@ -17,15 +17,18 @@ qt_add_library(quickshell-dbusmenu STATIC qt_add_qml_module(quickshell-dbusmenu URI Quickshell.DBusMenu VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-dbusmenu Quickshell) + install_qml_module(quickshell-dbusmenu) # dbus headers target_include_directories(quickshell-dbusmenu PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) -target_link_libraries(quickshell-dbusmenu PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-dbusmenu PRIVATE Qt::Quick Qt::DBus quickshell-dbus) -qs_pch(quickshell-dbusmenu) -qs_pch(quickshell-dbusmenuplugin) +qs_module_pch(quickshell-dbusmenu SET dbus) + +target_link_libraries(quickshell PRIVATE quickshell-dbusmenuplugin) diff --git a/src/dbus/properties.cpp b/src/dbus/properties.cpp index 7dac84ab..1a40ca23 100644 --- a/src/dbus/properties.cpp +++ b/src/dbus/properties.cpp @@ -16,7 +16,6 @@ #include <qloggingcategory.h> #include <qmetatype.h> #include <qobject.h> -#include <qpolygon.h> #include <qtmetamacros.h> #include <qvariant.h> diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 1d936d17..6299b397 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -23,14 +23,12 @@ qt_add_qml_module(quickshell-io install_qml_module(quickshell-io) -target_link_libraries(quickshell-io PRIVATE ${QT_DEPS}) -target_link_libraries(quickshell-io-init PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-io PRIVATE Qt::Quick) +target_link_libraries(quickshell-io-init PRIVATE Qt::Qml) target_link_libraries(quickshell PRIVATE quickshell-ioplugin quickshell-io-init) -qs_pch(quickshell-io) -qs_pch(quickshell-ioplugin) -qs_pch(quickshell-io-init) +qs_module_pch(quickshell-io) if (BUILD_TESTING) add_subdirectory(test) diff --git a/src/io/test/CMakeLists.txt b/src/io/test/CMakeLists.txt index 0c0cfc55..4ab51739 100644 --- a/src/io/test/CMakeLists.txt +++ b/src/io/test/CMakeLists.txt @@ -1,6 +1,6 @@ function (qs_test name) add_executable(${name} ${ARGN}) - target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test) + target_link_libraries(${name} PRIVATE Qt::Quick Qt::Network Qt::Test) add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>) endfunction() diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt index ff6093c6..e3f9e25f 100644 --- a/src/ipc/CMakeLists.txt +++ b/src/ipc/CMakeLists.txt @@ -2,6 +2,8 @@ qt_add_library(quickshell-ipc STATIC ipc.cpp ) -target_link_libraries(quickshell-ipc PRIVATE ${QT_DEPS}) +qs_pch(quickshell-ipc) + +target_link_libraries(quickshell-ipc PRIVATE Qt::Quick Qt::Network) target_link_libraries(quickshell PRIVATE quickshell-ipc) diff --git a/src/launch/CMakeLists.txt b/src/launch/CMakeLists.txt new file mode 100644 index 00000000..4db11bf0 --- /dev/null +++ b/src/launch/CMakeLists.txt @@ -0,0 +1,23 @@ +find_package(CLI11 CONFIG REQUIRED) + +qt_add_library(quickshell-launch STATIC + parsecommand.cpp + command.cpp + launch.cpp + main.cpp +) + +target_link_libraries(quickshell-launch PRIVATE + Qt::Quick Qt::Widgets CLI11::CLI11 quickshell-build +) + +qs_add_pchset(launch + DEPENDENCIES Qt::Core CLI11::CLI11 + HEADERS + <CLI/App.hpp> + <qcoreapplication.h> +) + +qs_pch(quickshell-launch SET launch) + +target_link_libraries(quickshell PRIVATE quickshell-launch) diff --git a/src/launch/command.cpp b/src/launch/command.cpp new file mode 100644 index 00000000..83001037 --- /dev/null +++ b/src/launch/command.cpp @@ -0,0 +1,448 @@ +#include <algorithm> +#include <array> +#include <cerrno> +#include <cstdio> +#include <cstring> + +#include <qconfig.h> +#include <qcontainerfwd.h> +#include <qcoreapplication.h> +#include <qcryptographichash.h> +#include <qdatetime.h> +#include <qdebug.h> +#include <qdir.h> +#include <qfileinfo.h> +#include <qjsonarray.h> +#include <qjsondocument.h> +#include <qjsonobject.h> +#include <qlogging.h> +#include <qloggingcategory.h> +#include <qnamespace.h> +#include <qstandardpaths.h> +#include <qtversion.h> +#include <unistd.h> + +#include "../core/instanceinfo.hpp" +#include "../core/logging.hpp" +#include "../core/paths.hpp" +#include "../io/ipccomm.hpp" +#include "../ipc/ipc.hpp" +#include "build.hpp" +#include "launch_p.hpp" + +namespace qs::launch { + +using qs::ipc::IpcClient; + +int readLogFile(CommandState& cmd); +int listInstances(CommandState& cmd); +int killInstances(CommandState& cmd); +int msgInstance(CommandState& cmd); +int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication); +int locateConfigFile(CommandState& cmd, QString& path); + +int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { + auto state = CommandState(); + if (auto ret = parseCommand(argc, argv, state); ret != 0) return ret; + + if (state.misc.checkCompat) { + if (strcmp(qVersion(), QT_VERSION_STR) != 0) { + QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt " + << QT_VERSION_STR << " but the system has updated to Qt " << qVersion() + << " without rebuilding the package. This is likely to cause crashes, so " + "you must rebuild the quickshell package.\n"; + return 1; + } + + return 0; + } + + // Has to happen before extra threads are spawned. + if (state.misc.daemonize) { + auto closepipes = std::array<int, 2>(); + if (pipe(closepipes.data()) == -1) { + qFatal().nospace() << "Failed to create messaging pipes for daemon with error " << errno + << ": " << qt_error_string(); + } + + DAEMON_PIPE = closepipes[1]; + + pid_t pid = fork(); // NOLINT (include) + + if (pid == -1) { + qFatal().nospace() << "Failed to fork daemon with error " << errno << ": " + << qt_error_string(); + } else if (pid == 0) { + close(closepipes[0]); + + if (setsid() == -1) { + qFatal().nospace() << "Failed to setsid with error " << errno << ": " << qt_error_string(); + } + } else { + close(closepipes[1]); + + int ret = 0; + if (read(closepipes[0], &ret, sizeof(int)) == -1) { + qFatal() << "Failed to wait for daemon launch (it may have crashed)"; + } + + return ret; + } + } + + { + auto level = state.log.verbosity == 0 ? QtWarningMsg + : state.log.verbosity == 1 ? QtInfoMsg + : QtDebugMsg; + + LogManager::init( + !state.log.noColor, + state.log.timestamp, + state.log.sparse, + level, + *state.log.rules, + *state.subcommand.log ? "READER" : "" + ); + } + + if (state.misc.printVersion) { + qCInfo(logBare).noquote().nospace() << "quickshell pre-release, revision " << GIT_REVISION + << ", distributed by: " << DISTRIBUTOR; + + if (state.log.verbosity > 1) { + qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR; + qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion(); + qCInfo(logBare).noquote() << "Compiler:" << COMPILER; + qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS; + } + + if (state.log.verbosity > 0) { + qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE; + qCInfo(logBare).noquote() << "Build configuration:"; + qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION; + } + } else if (*state.subcommand.log) { + return readLogFile(state); + } else if (*state.subcommand.list) { + return listInstances(state); + } else if (*state.subcommand.kill) { + return killInstances(state); + } else if (*state.subcommand.msg) { + return msgInstance(state); + } else { + if (strcmp(qVersion(), QT_VERSION_STR) != 0) { + qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR + << "but the system has updated to Qt" << qVersion() + << "without rebuilding the package. This is likely to cause crashes, so " + "the quickshell package must be rebuilt.\n"; + } + + return launchFromCommand(state, coreApplication); + } + + return 0; +} + +int locateConfigFile(CommandState& cmd, QString& path) { + if (!cmd.config.path->isEmpty()) { + path = *cmd.config.path; + } else { + auto manifestPath = *cmd.config.manifest; + if (manifestPath.isEmpty()) { + auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); + auto path = configDir.filePath("manifest.conf"); + if (QFileInfo(path).isFile()) manifestPath = path; + } + + if (!manifestPath.isEmpty()) { + auto file = QFile(manifestPath); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + auto stream = QTextStream(&file); + while (!stream.atEnd()) { + auto line = stream.readLine(); + if (line.trimmed().startsWith("#")) continue; + if (line.trimmed().isEmpty()) continue; + + auto split = line.split('='); + if (split.length() != 2) { + qCritical() << "Manifest line not in expected format 'name = relativepath':" << line; + return -1; + } + + if (split[0].trimmed() == *cmd.config.name) { + path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed()); + break; + } + } + + if (path.isEmpty()) { + qCCritical(logBare) << "Configuration" << *cmd.config.name + << "not found when searching manifest" << manifestPath; + return -1; + } + } else { + qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest; + return -1; + } + } else { + auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); + + if (cmd.config.name->isEmpty()) { + path = configDir.path(); + } else { + path = configDir.filePath(*cmd.config.name); + } + } + } + + if (QFileInfo(path).isDir()) { + path = QDir(path).filePath("shell.qml"); + } + + if (!QFileInfo(path).isFile()) { + qCCritical(logBare) << "Could not open config file at" << path; + return -1; + } + + path = QFileInfo(path).canonicalFilePath(); + + return 0; +} + +void sortInstances(QVector<InstanceLockInfo>& list) { + std::sort(list.begin(), list.end(), [](const InstanceLockInfo& a, const InstanceLockInfo& b) { + return a.instance.launchTime < b.instance.launchTime; + }); +}; + +int selectInstance(CommandState& cmd, InstanceLockInfo* instance) { + auto* basePath = QsPaths::instance()->baseRunDir(); + if (!basePath) return -1; + + QString path; + + if (cmd.instance.pid != -1) { + path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid)); + if (!QsPaths::checkLock(path, instance)) { + qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid; + return -1; + } + } else if (!cmd.instance.id->isEmpty()) { + path = basePath->filePath("by-pid"); + auto instances = QsPaths::collectInstances(path); + + auto itr = + std::remove_if(instances.begin(), instances.end(), [&](const InstanceLockInfo& info) { + return !info.instance.instanceId.startsWith(*cmd.instance.id); + }); + + instances.erase(itr, instances.end()); + + if (instances.isEmpty()) { + qCInfo(logBare) << "No running instances start with" << *cmd.instance.id; + return -1; + } else if (instances.length() != 1) { + qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id; + + for (auto& instance: instances) { + qCInfo(logBare).noquote() << " -" << instance.instance.instanceId; + } + + return -1; + } else { + *instance = instances.value(0); + } + } else { + QString configFilePath; + auto r = locateConfigFile(cmd, configFilePath); + if (r != 0) return r; + + auto pathId = + QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex(); + + path = QDir(basePath->filePath("by-path")).filePath(pathId); + + auto instances = QsPaths::collectInstances(path); + sortInstances(instances); + + if (instances.isEmpty()) { + qCInfo(logBare) << "No running instances for" << configFilePath; + return -1; + } + + *instance = instances.value(0); + } + + return 0; +} + +int readLogFile(CommandState& cmd) { + auto path = *cmd.log.file; + + if (path.isEmpty()) { + InstanceLockInfo instance; + auto r = selectInstance(cmd, &instance); + if (r != 0) return r; + + path = QDir(QsPaths::basePath(instance.instance.instanceId)).filePath("log.qslog"); + } + + auto file = QFile(path); + if (!file.open(QFile::ReadOnly)) { + qCCritical(logBare) << "Failed to open log file" << path; + return -1; + } + + return qs::log::readEncodedLogs( + &file, + path, + cmd.log.timestamp, + cmd.log.tail, + cmd.log.follow, + *cmd.log.readoutRules + ) + ? 0 + : -1; +} + +int listInstances(CommandState& cmd) { + auto* basePath = QsPaths::instance()->baseRunDir(); + if (!basePath) return -1; // NOLINT + + QString path; + QString configFilePath; + if (cmd.instance.all) { + path = basePath->filePath("by-pid"); + } else { + auto r = locateConfigFile(cmd, configFilePath); + + if (r != 0) { + qCInfo(logBare) << "Use --all to list all instances."; + return r; + } + + auto pathId = + QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex(); + + path = QDir(basePath->filePath("by-path")).filePath(pathId); + } + + auto instances = QsPaths::collectInstances(path); + + if (instances.isEmpty()) { + if (cmd.instance.all) { + qCInfo(logBare) << "No running instances."; + } else { + qCInfo(logBare) << "No running instances for" << configFilePath; + qCInfo(logBare) << "Use --all to list all instances."; + } + } else { + sortInstances(instances); + + if (cmd.output.json) { + auto array = QJsonArray(); + + for (auto& instance: instances) { + auto json = QJsonObject(); + + json["id"] = instance.instance.instanceId; + json["pid"] = instance.pid; + json["shell_id"] = instance.instance.shellId; + json["config_path"] = instance.instance.configPath; + json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate); + + array.push_back(json); + } + + auto document = QJsonDocument(array); + QTextStream(stdout) << document.toJson(QJsonDocument::Indented); + } else { + for (auto& instance: instances) { + auto launchTimeStr = instance.instance.launchTime.toString("yyyy-MM-dd hh:mm:ss"); + + auto runSeconds = instance.instance.launchTime.secsTo(QDateTime::currentDateTime()); + auto remSeconds = runSeconds % 60; + auto runMinutes = (runSeconds - remSeconds) / 60; + auto remMinutes = runMinutes % 60; + auto runHours = (runMinutes - remMinutes) / 60; + auto runtimeStr = QString("%1 hours, %2 minutes, %3 seconds") + .arg(runHours) + .arg(remMinutes) + .arg(remSeconds); + + qCInfo(logBare).noquote().nospace() + << "Instance " << instance.instance.instanceId << ":\n" + << " Process ID: " << instance.pid << '\n' + << " Shell ID: " << instance.instance.shellId << '\n' + << " Config path: " << instance.instance.configPath << '\n' + << " Launch time: " << launchTimeStr << " (running for " << runtimeStr << ")\n"; + } + } + } + + return 0; +} + +int killInstances(CommandState& cmd) { + InstanceLockInfo instance; + auto r = selectInstance(cmd, &instance); + if (r != 0) return r; + + return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) { + client.kill(); + qCInfo(logBare).noquote() << "Killed" << instance.instance.instanceId; + }); +} + +int msgInstance(CommandState& cmd) { + InstanceLockInfo instance; + auto r = selectInstance(cmd, &instance); + if (r != 0) return r; + + return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) { + if (cmd.ipc.info) { + return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.function); + } else { + QVector<QString> arguments; + for (auto& arg: cmd.ipc.arguments) { + arguments += *arg; + } + + return qs::io::ipc::comm::callFunction( + &client, + *cmd.ipc.target, + *cmd.ipc.function, + arguments + ); + } + + return -1; + }); +} + +int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) { + QString configPath; + + auto r = locateConfigFile(cmd, configPath); + if (r != 0) return r; + + { + InstanceLockInfo info; + if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) { + qCInfo(logBare) << "An instance of this configuration is already running."; + return 0; + } + } + + return launch( + { + .configPath = configPath, + .debugPort = cmd.debug.port, + .waitForDebug = cmd.debug.wait, + }, + cmd.exec.argv, + coreApplication + ); +} + +} // namespace qs::launch diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp new file mode 100644 index 00000000..30c87a62 --- /dev/null +++ b/src/launch/launch.cpp @@ -0,0 +1,238 @@ +#include <qapplication.h> +#include <qcoreapplication.h> +#include <qcryptographichash.h> +#include <qdebug.h> +#include <qdir.h> +#include <qfile.h> +#include <qguiapplication.h> +#include <qhash.h> +#include <qlist.h> +#include <qlogging.h> +#include <qnamespace.h> +#include <qqmldebug.h> +#include <qquickwindow.h> +#include <qstring.h> +#include <qtenvironmentvariables.h> +#include <qtextstream.h> +#include <unistd.h> + +#include "../core/common.hpp" +#include "../core/instanceinfo.hpp" +#include "../core/logging.hpp" +#include "../core/paths.hpp" +#include "../core/plugin.hpp" +#include "../core/rootwrapper.hpp" +#include "../ipc/ipc.hpp" +#include "build.hpp" +#include "launch_p.hpp" + +#if CRASH_REPORTER +#include "../crash/handler.hpp" +#endif + +namespace qs::launch { + +template <typename T> +QString base36Encode(T number) { + const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz"; + QString result; + + do { + result.prepend(digits[number % 36]); + number /= 36; + } while (number > 0); + + for (auto i = 0; i < result.length() / 2; i++) { + auto opposite = result.length() - i - 1; + auto c = result.at(i); + result[i] = result.at(opposite); + result[opposite] = c; + } + + return result; +} + +int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) { + auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex(); + auto shellId = QString(pathId); + + qInfo() << "Launching config:" << args.configPath; + + auto file = QFile(args.configPath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + qCritical() << "Could not open config file" << args.configPath; + return -1; + } + + struct { + bool useQApplication = false; + bool nativeTextRendering = false; + bool desktopSettingsAware = true; + QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); + QHash<QString, QString> envOverrides; + } pragmas; + + auto stream = QTextStream(&file); + while (!stream.atEnd()) { + auto line = stream.readLine().trimmed(); + if (line.startsWith("//@ pragma ")) { + auto pragma = line.sliced(11).trimmed(); + + if (pragma == "UseQApplication") pragmas.useQApplication = true; + else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true; + else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false; + else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10); + else if (pragma.startsWith("Env ")) { + auto envPragma = pragma.sliced(4); + auto splitIdx = envPragma.indexOf('='); + + if (splitIdx == -1) { + qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'"; + return -1; + } + + auto var = envPragma.sliced(0, splitIdx).trimmed(); + auto val = envPragma.sliced(splitIdx + 1).trimmed(); + pragmas.envOverrides.insert(var, val); + } else if (pragma.startsWith("ShellId ")) { + shellId = pragma.sliced(8).trimmed(); + } else { + qCritical() << "Unrecognized pragma" << pragma; + return -1; + } + } else if (line.startsWith("import")) break; + } + + file.close(); + + if (!pragmas.iconTheme.isEmpty()) { + QIcon::setThemeName(pragmas.iconTheme); + } + + qInfo() << "Shell ID:" << shellId << "Path ID" << pathId; + + auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch(); + InstanceInfo::CURRENT = InstanceInfo { + .instanceId = base36Encode(getpid()) + base36Encode(launchTime), + .configPath = args.configPath, + .shellId = shellId, + .launchTime = qs::Common::LAUNCH_TIME, + }; + +#if CRASH_REPORTER + auto crashHandler = crash::CrashHandler(); + crashHandler.init(); + + { + auto* log = LogManager::instance(); + crashHandler.setRelaunchInfo({ + .instance = InstanceInfo::CURRENT, + .noColor = !log->colorLogs, + .timestamp = log->timestampLogs, + .sparseLogsOnly = log->isSparse(), + .defaultLogLevel = log->defaultLevel(), + .logRules = log->rulesString(), + }); + } +#endif + + QsPaths::init(shellId, pathId); + QsPaths::instance()->linkRunDir(); + QsPaths::instance()->linkPathDir(); + LogManager::initFs(); + + for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) { + qputenv(var.toUtf8(), val.toUtf8()); + } + + // The qml engine currently refuses to cache non file (qsintercept) paths. + + // if (auto* cacheDir = QsPaths::instance()->cacheDir()) { + // auto qmlCacheDir = cacheDir->filePath("qml-engine-cache"); + // qputenv("QML_DISK_CACHE_PATH", qmlCacheDir.toLocal8Bit()); + // + // if (!qEnvironmentVariableIsSet("QML_DISK_CACHE")) { + // qputenv("QML_DISK_CACHE", "aot,qmlc"); + // } + // } + + // While the simple animation driver can lead to better animations in some cases, + // it also can cause excessive repainting at excessively high framerates which can + // lead to noticeable amounts of gpu usage, including overheating on some systems. + // This gets worse the more windows are open, as repaints trigger on all of them for + // some reason. See QTBUG-126099 for details. + + // if (!qEnvironmentVariableIsSet("QSG_USE_SIMPLE_ANIMATION_DRIVER")) { + // qputenv("QSG_USE_SIMPLE_ANIMATION_DRIVER", "1"); + // } + + // Some programs place icons in the pixmaps folder instead of the icons folder. + // This seems to be controlled by the QPA and qt6ct does not provide it. + { + QList<QString> dataPaths; + + if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { + auto var = qEnvironmentVariable("XDG_DATA_DIRS"); + dataPaths = var.split(u':', Qt::SkipEmptyParts); + } else { + dataPaths.push_back("/usr/local/share"); + dataPaths.push_back("/usr/share"); + } + + auto fallbackPaths = QIcon::fallbackSearchPaths(); + + for (auto& path: dataPaths) { + auto newPath = QDir(path).filePath("pixmaps"); + + if (!fallbackPaths.contains(newPath)) { + fallbackPaths.push_back(newPath); + } + } + + QIcon::setFallbackSearchPaths(fallbackPaths); + } + + QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware); + + delete coreApplication; + + QGuiApplication* app = nullptr; + auto qArgC = 0; + + if (pragmas.useQApplication) { + app = new QApplication(qArgC, argv); + } else { + app = new QGuiApplication(qArgC, argv); + } + + if (args.debugPort != -1) { + QQmlDebuggingEnabler::enableDebugging(true); + auto wait = args.waitForDebug ? QQmlDebuggingEnabler::WaitForClient + : QQmlDebuggingEnabler::DoNotWaitForClient; + QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait); + } + + QuickshellPlugin::initPlugins(); + + // Base window transparency appears to be additive. + // Use a fully transparent window with a colored rect. + QQuickWindow::setDefaultAlphaBuffer(true); + + if (pragmas.nativeTextRendering) { + QQuickWindow::setTextRenderType(QQuickWindow::NativeTextRendering); + } + + qs::ipc::IpcServer::start(); + QsPaths::instance()->createLock(); + + auto root = RootWrapper(args.configPath, shellId); + QGuiApplication::setQuitOnLastWindowClosed(false); + + exitDaemon(0); + + auto code = QGuiApplication::exec(); + delete app; + return code; +} + +} // namespace qs::launch diff --git a/src/launch/launch_p.hpp b/src/launch/launch_p.hpp new file mode 100644 index 00000000..d1916d50 --- /dev/null +++ b/src/launch/launch_p.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include <string> + +#include <CLI/App.hpp> +#include <qcoreapplication.h> +#include <qstring.h> + +namespace qs::launch { + +extern int DAEMON_PIPE; // NOLINT + +class QStringOption { +public: + QStringOption() = default; + QStringOption& operator=(const std::string& str) { + this->str = QString::fromStdString(str); + return *this; + } + + QString& operator*() { return this->str; } + QString* operator->() { return &this->str; } + +private: + QString str; +}; + +struct CommandState { + struct { + int argc = 0; + char** argv = nullptr; + } exec; + + struct { + bool timestamp = false; + bool noColor = !qEnvironmentVariableIsEmpty("NO_COLOR"); + bool sparse = false; + size_t verbosity = 0; + int tail = 0; + bool follow = false; + QStringOption rules; + QStringOption readoutRules; + QStringOption file; + } log; + + struct { + QStringOption path; + QStringOption manifest; + QStringOption name; + } config; + + struct { + int port = -1; + bool wait = false; + } debug; + + struct { + QStringOption id; + pid_t pid = -1; // NOLINT (include) + bool all = false; + } instance; + + struct { + bool json = false; + } output; + + struct { + bool info = false; + QStringOption target; + QStringOption function; + std::vector<QStringOption> arguments; + } ipc; + + struct { + CLI::App* log = nullptr; + CLI::App* list = nullptr; + CLI::App* kill = nullptr; + CLI::App* msg = nullptr; + } subcommand; + + struct { + bool checkCompat = false; + bool printVersion = false; + bool killAll = false; + bool noDuplicate = false; + bool daemonize = false; + } misc; +}; + +struct LaunchArgs { + QString configPath; + int debugPort = -1; + bool waitForDebug = false; +}; + +void exitDaemon(int code); + +int parseCommand(int argc, char** argv, CommandState& state); +int runCommand(int argc, char** argv, QCoreApplication* coreApplication); + +int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication); + +} // namespace qs::launch diff --git a/src/launch/main.cpp b/src/launch/main.cpp new file mode 100644 index 00000000..3a2b5822 --- /dev/null +++ b/src/launch/main.cpp @@ -0,0 +1,116 @@ +#include "main.hpp" +#include <cerrno> + +#include <fcntl.h> +#include <qcoreapplication.h> +#include <qdatastream.h> +#include <qdatetime.h> +#include <qdebug.h> +#include <qlogging.h> +#include <qtenvironmentvariables.h> +#include <unistd.h> + +#include "../core/instanceinfo.hpp" +#include "../core/logging.hpp" +#include "../core/paths.hpp" +#include "build.hpp" +#include "launch_p.hpp" + +#if CRASH_REPORTER +#include "../crash/main.hpp" +#endif + +namespace qs::launch { + +void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication); + +int DAEMON_PIPE = -1; // NOLINT + +void exitDaemon(int code) { + if (DAEMON_PIPE == -1) return; + + if (write(DAEMON_PIPE, &code, sizeof(int)) == -1) { + qCritical().nospace() << "Failed to write daemon exit command with error code " << errno << ": " + << qt_error_string(); + } + + close(DAEMON_PIPE); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stdin"; + } + + if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stdout"; + } + + if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stderr"; + } +} + +void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) { +#if CRASH_REPORTER + auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD"); + + if (!lastInfoFdStr.isEmpty()) { + auto lastInfoFd = lastInfoFdStr.toInt(); + + QFile file; + file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle); + file.seek(0); + + auto ds = QDataStream(&file); + RelaunchInfo info; + ds >> info; + + LogManager::init( + !info.noColor, + info.timestamp, + info.sparseLogsOnly, + info.defaultLogLevel, + info.logRules + ); + + qCritical().nospace() << "Quickshell has crashed under pid " + << qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt() + << " (Coredumps will be available under that pid.)"; + + qCritical() << "Further crash information is stored under" + << QsPaths::crashDir(info.instance.instanceId).path(); + + if (info.instance.launchTime.msecsTo(QDateTime::currentDateTime()) < 10000) { + qCritical() << "Quickshell crashed within 10 seconds of launching. Not restarting to avoid " + "a crash loop."; + exit(-1); // NOLINT + } else { + qCritical() << "Quickshell has been restarted."; + + launch({.configPath = info.instance.configPath}, argv, coreApplication); + } + } +#endif +} + +int main(int argc, char** argv) { + QCoreApplication::setApplicationName("quickshell"); + +#if CRASH_REPORTER + qsCheckCrash(argc, argv); +#endif + + auto qArgC = 1; + auto* coreApplication = new QCoreApplication(qArgC, argv); + + checkCrashRelaunch(argv, coreApplication); + auto code = runCommand(argc, argv, coreApplication); + + exitDaemon(code); + return code; +} + +} // namespace qs::launch diff --git a/src/launch/main.hpp b/src/launch/main.hpp new file mode 100644 index 00000000..e9d22902 --- /dev/null +++ b/src/launch/main.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace qs::launch { + +int main(int argc, char** argv); + +} diff --git a/src/launch/parsecommand.cpp b/src/launch/parsecommand.cpp new file mode 100644 index 00000000..14fd9203 --- /dev/null +++ b/src/launch/parsecommand.cpp @@ -0,0 +1,196 @@ +#include <cstddef> +#include <limits> + +#include <CLI/App.hpp> +#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes +#include <CLI/Validators.hpp> + +#include "launch_p.hpp" + +namespace qs::launch { + +int parseCommand(int argc, char** argv, CommandState& state) { + state.exec = { + .argc = argc, + .argv = argv, + }; + + auto addConfigSelection = [&](CLI::App* cmd) { + auto* group = cmd->add_option_group("Config Selection") + ->description("If no options in this group are specified,\n" + "$XDG_CONFIG_HOME/quickshell/shell.qml will be used."); + + auto* path = group->add_option("-p,--path", state.config.path) + ->description("Path to a QML file.") + ->envname("QS_CONFIG_PATH"); + + group->add_option("-m,--manifest", state.config.manifest) + ->description("Path to a quickshell manifest.\n" + "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf") + ->envname("QS_MANIFEST") + ->excludes(path); + + group->add_option("-c,--config", state.config.name) + ->description("Name of a quickshell configuration to run.\n" + "If -m is specified, this is a configuration in the manifest,\n" + "otherwise it is the name of a folder in $XDG_CONFIG_HOME/quickshell.") + ->envname("QS_CONFIG_NAME"); + + return group; + }; + + auto addDebugOptions = [&](CLI::App* cmd) { + auto* group = cmd->add_option_group("Debugging", "Options for QML debugging."); + + auto* debug = group->add_option("--debug", state.debug.port) + ->description("Open the given port for a QML debugger connection.") + ->check(CLI::Range(0, 65535)); + + group->add_flag("--waitfordebug", state.debug.wait) + ->description("Wait for a QML debugger to connect before executing the configuration.") + ->needs(debug); + + return group; + }; + + auto addLoggingOptions = [&](CLI::App* cmd, bool noGroup, bool noDisplay = false) { + auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging"); + + group->add_flag("--no-color", state.log.noColor) + ->description("Disables colored logging.\n" + "Colored logging can also be disabled by specifying a non empty value\n" + "for the NO_COLOR environment variable."); + + group->add_flag("--log-times", state.log.timestamp) + ->description("Log timestamps with each message."); + + group->add_option("--log-rules", state.log.rules) + ->description("Log rules to apply, in the format of QT_LOGGING_RULES."); + + group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; }) + ->description("Increases log verbosity.\n" + "-v will show INFO level internal logs.\n" + "-vv will show DEBUG level internal logs."); + + auto* hgroup = cmd->add_option_group(""); + hgroup->add_flag("--no-detailed-logs", state.log.sparse); + }; + + auto addInstanceSelection = [&](CLI::App* cmd) { + auto* group = cmd->add_option_group("Instance Selection"); + + group->add_option("-i,--id", state.instance.id) + ->description("The instance id to operate on.\n" + "You may also use a substring the id as long as it is unique,\n" + "for example \"abc\" will select \"abcdefg\"."); + + group->add_option("--pid", state.instance.pid) + ->description("The process id of the instance to operate on."); + + return group; + }; + + auto cli = CLI::App(); + + // Require 0-1 subcommands. Without this, positionals can be parsed as more subcommands. + cli.require_subcommand(0, 1); + + addConfigSelection(&cli); + addLoggingOptions(&cli, false); + addDebugOptions(&cli); + + { + cli.add_option_group("")->add_flag("--private-check-compat", state.misc.checkCompat); + + cli.add_flag("-V,--version", state.misc.printVersion) + ->description("Print quickshell's version and exit."); + + cli.add_flag("-n,--no-duplicate", state.misc.noDuplicate) + ->description("Exit immediately if another instance of the given config is running."); + + cli.add_flag("-d,--daemonize", state.misc.daemonize) + ->description("Detach from the controlling terminal."); + } + + { + auto* sub = cli.add_subcommand("log", "Print quickshell logs."); + + auto* file = sub->add_option("file", state.log.file, "Log file to read."); + + sub->add_option("-t,--tail", state.log.tail) + ->description("Maximum number of lines to print, starting from the bottom.") + ->check(CLI::Range(1, std::numeric_limits<int>::max(), "INT > 0")); + + sub->add_flag("-f,--follow", state.log.follow) + ->description("Keep reading the log until the logging process terminates."); + + sub->add_option("-r,--rules", state.log.readoutRules, "Log file to read.") + ->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES."); + + auto* instance = addInstanceSelection(sub)->excludes(file); + addConfigSelection(sub)->excludes(instance)->excludes(file); + addLoggingOptions(sub, false); + + state.subcommand.log = sub; + } + + { + auto* sub = cli.add_subcommand("list", "List running quickshell instances."); + + auto* all = sub->add_flag("-a,--all", state.instance.all) + ->description("List all instances.\n" + "If unspecified, only instances of" + "the selected config will be listed."); + + sub->add_flag("-j,--json", state.output.json, "Output the list as a json."); + + addConfigSelection(sub)->excludes(all); + addLoggingOptions(sub, false, true); + + state.subcommand.list = sub; + } + + { + auto* sub = cli.add_subcommand("kill", "Kill quickshell instances."); + //sub->add_flag("-a,--all", "Kill all matching instances instead of just one."); + auto* instance = addInstanceSelection(sub); + addConfigSelection(sub)->excludes(instance); + addLoggingOptions(sub, false, true); + + state.subcommand.kill = sub; + } + + { + auto* sub = cli.add_subcommand("msg", "Send messages to IpcHandlers.")->require_option(); + + auto* target = sub->add_option("target", state.ipc.target, "The target to message."); + + auto* function = sub->add_option("function", state.ipc.function) + ->description("The function to call in the target.") + ->needs(target); + + auto* arguments = sub->add_option("arguments", state.ipc.arguments) + ->description("Arguments to the called function.") + ->needs(function) + ->allow_extra_args(); + + sub->add_flag("-s,--show", state.ipc.info) + ->description("Print information about a function or target if given, or all available " + "targets if not.") + ->excludes(arguments); + + auto* instance = addInstanceSelection(sub); + addConfigSelection(sub)->excludes(instance); + addLoggingOptions(sub, false, true); + + sub->require_option(); + + state.subcommand.msg = sub; + } + + CLI11_PARSE(cli, argc, argv); + + return 0; +} + +} // namespace qs::launch diff --git a/src/main.cpp b/src/main.cpp index f18c2341..e0ce937f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,1032 +1,3 @@ -#include <algorithm> -#include <array> -#include <cerrno> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <limits> -#include <string> -#include <vector> - -#include <CLI/App.hpp> -#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes -#include <CLI/Validators.hpp> -#include <fcntl.h> -#include <qapplication.h> -#include <qconfig.h> -#include <qcontainerfwd.h> -#include <qcoreapplication.h> -#include <qcryptographichash.h> -#include <qdatastream.h> -#include <qdatetime.h> -#include <qdir.h> -#include <qfileinfo.h> -#include <qguiapplication.h> -#include <qhash.h> -#include <qicon.h> -#include <qjsonarray.h> -#include <qjsondocument.h> -#include <qjsonobject.h> -#include <qlist.h> -#include <qlogging.h> -#include <qloggingcategory.h> -#include <qnamespace.h> -#include <qqmldebug.h> -#include <qquickwindow.h> -#include <qstandardpaths.h> -#include <qstring.h> -#include <qtenvironmentvariables.h> -#include <qtextstream.h> -#include <qtversion.h> -#include <unistd.h> - -#include "build.hpp" -#include "core/common.hpp" -#include "core/instanceinfo.hpp" -#include "core/logging.hpp" -#include "core/paths.hpp" -#include "core/plugin.hpp" -#include "core/rootwrapper.hpp" -#include "io/ipccomm.hpp" -#include "ipc/ipc.hpp" - -#if CRASH_REPORTER -#include "crash/handler.hpp" -#include "crash/main.hpp" -#endif - -namespace qs::launch { - -using qs::ipc::IpcClient; - -void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication); -int runCommand(int argc, char** argv, QCoreApplication* coreApplication); - -int DAEMON_PIPE = -1; // NOLINT -void exitDaemon(int code) { - if (DAEMON_PIPE == -1) return; - - if (write(DAEMON_PIPE, &code, sizeof(int)) == -1) { - qCritical().nospace() << "Failed to write daemon exit command with error code " << errno << ": " - << qt_error_string(); - } - - close(DAEMON_PIPE); - - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); - - if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stdin"; - } - - if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stdout"; - } - - if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT - qFatal() << "Failed to open /dev/null on stderr"; - } -} - -int main(int argc, char** argv) { - QCoreApplication::setApplicationName("quickshell"); - -#if CRASH_REPORTER - qsCheckCrash(argc, argv); -#endif - - auto qArgC = 1; - auto* coreApplication = new QCoreApplication(qArgC, argv); - - checkCrashRelaunch(argv, coreApplication); - auto code = runCommand(argc, argv, coreApplication); - - exitDaemon(code); - return code; -} - -class QStringOption { -public: - QStringOption() = default; - QStringOption& operator=(const std::string& str) { - this->str = QString::fromStdString(str); - return *this; - } - - QString& operator*() { return this->str; } - QString* operator->() { return &this->str; } - -private: - QString str; -}; - -struct CommandState { - struct { - int argc = 0; - char** argv = nullptr; - } exec; - - struct { - bool timestamp = false; - bool noColor = !qEnvironmentVariableIsEmpty("NO_COLOR"); - bool sparse = false; - size_t verbosity = 0; - int tail = 0; - bool follow = false; - QStringOption rules; - QStringOption readoutRules; - QStringOption file; - } log; - - struct { - QStringOption path; - QStringOption manifest; - QStringOption name; - } config; - - struct { - int port = -1; - bool wait = false; - } debug; - - struct { - QStringOption id; - pid_t pid = -1; // NOLINT (include) - bool all = false; - } instance; - - struct { - bool json = false; - } output; - - struct { - bool info = false; - QStringOption target; - QStringOption function; - std::vector<QStringOption> arguments; - } ipc; - - struct { - CLI::App* log = nullptr; - CLI::App* list = nullptr; - CLI::App* kill = nullptr; - CLI::App* msg = nullptr; - } subcommand; - - struct { - bool checkCompat = false; - bool printVersion = false; - bool killAll = false; - bool noDuplicate = false; - bool daemonize = false; - } misc; -}; - -int readLogFile(CommandState& cmd); -int listInstances(CommandState& cmd); -int killInstances(CommandState& cmd); -int msgInstance(CommandState& cmd); -int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication); - -struct LaunchArgs { - QString configPath; - int debugPort = -1; - bool waitForDebug = false; -}; - -int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication); - -int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { - auto state = CommandState(); - - state.exec = { - .argc = argc, - .argv = argv, - }; - - auto addConfigSelection = [&](CLI::App* cmd) { - auto* group = cmd->add_option_group("Config Selection") - ->description("If no options in this group are specified,\n" - "$XDG_CONFIG_HOME/quickshell/shell.qml will be used."); - - auto* path = group->add_option("-p,--path", state.config.path) - ->description("Path to a QML file.") - ->envname("QS_CONFIG_PATH"); - - group->add_option("-m,--manifest", state.config.manifest) - ->description("Path to a quickshell manifest.\n" - "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf") - ->envname("QS_MANIFEST") - ->excludes(path); - - group->add_option("-c,--config", state.config.name) - ->description("Name of a quickshell configuration to run.\n" - "If -m is specified, this is a configuration in the manifest,\n" - "otherwise it is the name of a folder in $XDG_CONFIG_HOME/quickshell.") - ->envname("QS_CONFIG_NAME"); - - return group; - }; - - auto addDebugOptions = [&](CLI::App* cmd) { - auto* group = cmd->add_option_group("Debugging", "Options for QML debugging."); - - auto* debug = group->add_option("--debug", state.debug.port) - ->description("Open the given port for a QML debugger connection.") - ->check(CLI::Range(0, 65535)); - - group->add_flag("--waitfordebug", state.debug.wait) - ->description("Wait for a QML debugger to connect before executing the configuration.") - ->needs(debug); - - return group; - }; - - auto addLoggingOptions = [&](CLI::App* cmd, bool noGroup, bool noDisplay = false) { - auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging"); - - group->add_flag("--no-color", state.log.noColor) - ->description("Disables colored logging.\n" - "Colored logging can also be disabled by specifying a non empty value\n" - "for the NO_COLOR environment variable."); - - group->add_flag("--log-times", state.log.timestamp) - ->description("Log timestamps with each message."); - - group->add_option("--log-rules", state.log.rules) - ->description("Log rules to apply, in the format of QT_LOGGING_RULES."); - - group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; }) - ->description("Increases log verbosity.\n" - "-v will show INFO level internal logs.\n" - "-vv will show DEBUG level internal logs."); - - auto* hgroup = cmd->add_option_group(""); - hgroup->add_flag("--no-detailed-logs", state.log.sparse); - }; - - auto addInstanceSelection = [&](CLI::App* cmd) { - auto* group = cmd->add_option_group("Instance Selection"); - - group->add_option("-i,--id", state.instance.id) - ->description("The instance id to operate on.\n" - "You may also use a substring the id as long as it is unique,\n" - "for example \"abc\" will select \"abcdefg\"."); - - group->add_option("--pid", state.instance.pid) - ->description("The process id of the instance to operate on."); - - return group; - }; - - auto cli = CLI::App(); - - // Require 0-1 subcommands. Without this, positionals can be parsed as more subcommands. - cli.require_subcommand(0, 1); - - addConfigSelection(&cli); - addLoggingOptions(&cli, false); - addDebugOptions(&cli); - - { - cli.add_option_group("")->add_flag("--private-check-compat", state.misc.checkCompat); - - cli.add_flag("-V,--version", state.misc.printVersion) - ->description("Print quickshell's version and exit."); - - cli.add_flag("-n,--no-duplicate", state.misc.noDuplicate) - ->description("Exit immediately if another instance of the given config is running."); - - cli.add_flag("-d,--daemonize", state.misc.daemonize) - ->description("Detach from the controlling terminal."); - } - - { - auto* sub = cli.add_subcommand("log", "Print quickshell logs."); - - auto* file = sub->add_option("file", state.log.file, "Log file to read."); - - sub->add_option("-t,--tail", state.log.tail) - ->description("Maximum number of lines to print, starting from the bottom.") - ->check(CLI::Range(1, std::numeric_limits<int>::max(), "INT > 0")); - - sub->add_flag("-f,--follow", state.log.follow) - ->description("Keep reading the log until the logging process terminates."); - - sub->add_option("-r,--rules", state.log.readoutRules, "Log file to read.") - ->description("Rules to apply to the log being read, in the format of QT_LOGGING_RULES."); - - auto* instance = addInstanceSelection(sub)->excludes(file); - addConfigSelection(sub)->excludes(instance)->excludes(file); - addLoggingOptions(sub, false); - - state.subcommand.log = sub; - } - - { - auto* sub = cli.add_subcommand("list", "List running quickshell instances."); - - auto* all = sub->add_flag("-a,--all", state.instance.all) - ->description("List all instances.\n" - "If unspecified, only instances of" - "the selected config will be listed."); - - sub->add_flag("-j,--json", state.output.json, "Output the list as a json."); - - addConfigSelection(sub)->excludes(all); - addLoggingOptions(sub, false, true); - - state.subcommand.list = sub; - } - - { - auto* sub = cli.add_subcommand("kill", "Kill quickshell instances."); - //sub->add_flag("-a,--all", "Kill all matching instances instead of just one."); - auto* instance = addInstanceSelection(sub); - addConfigSelection(sub)->excludes(instance); - addLoggingOptions(sub, false, true); - - state.subcommand.kill = sub; - } - - { - auto* sub = cli.add_subcommand("msg", "Send messages to IpcHandlers.")->require_option(); - - auto* target = sub->add_option("target", state.ipc.target, "The target to message."); - - auto* function = sub->add_option("function", state.ipc.function) - ->description("The function to call in the target.") - ->needs(target); - - auto* arguments = sub->add_option("arguments", state.ipc.arguments) - ->description("Arguments to the called function.") - ->needs(function) - ->allow_extra_args(); - - sub->add_flag("-s,--show", state.ipc.info) - ->description("Print information about a function or target if given, or all available " - "targets if not.") - ->excludes(arguments); - - auto* instance = addInstanceSelection(sub); - addConfigSelection(sub)->excludes(instance); - addLoggingOptions(sub, false, true); - - sub->require_option(); - - state.subcommand.msg = sub; - } - - CLI11_PARSE(cli, argc, argv); - - if (state.misc.checkCompat) { - if (strcmp(qVersion(), QT_VERSION_STR) != 0) { - QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt " - << QT_VERSION_STR << " but the system has updated to Qt " << qVersion() - << " without rebuilding the package. This is likely to cause crashes, so " - "you must rebuild the quickshell package.\n"; - return 1; - } - - return 0; - } - - // Has to happen before extra threads are spawned. - if (state.misc.daemonize) { - auto closepipes = std::array<int, 2>(); - if (pipe(closepipes.data()) == -1) { - qFatal().nospace() << "Failed to create messaging pipes for daemon with error " << errno - << ": " << qt_error_string(); - } - - DAEMON_PIPE = closepipes[1]; - - pid_t pid = fork(); // NOLINT (include) - - if (pid == -1) { - qFatal().nospace() << "Failed to fork daemon with error " << errno << ": " - << qt_error_string(); - } else if (pid == 0) { - close(closepipes[0]); - - if (setsid() == -1) { - qFatal().nospace() << "Failed to setsid with error " << errno << ": " << qt_error_string(); - } - } else { - close(closepipes[1]); - - int ret = 0; - if (read(closepipes[0], &ret, sizeof(int)) == -1) { - qFatal() << "Failed to wait for daemon launch (it may have crashed)"; - } - - return ret; - } - } - - { - auto level = state.log.verbosity == 0 ? QtWarningMsg - : state.log.verbosity == 1 ? QtInfoMsg - : QtDebugMsg; - - LogManager::init( - !state.log.noColor, - state.log.timestamp, - state.log.sparse, - level, - *state.log.rules, - *state.subcommand.log ? "READER" : "" - ); - } - - if (state.misc.printVersion) { - qCInfo(logBare).noquote().nospace() << "quickshell pre-release, revision " << GIT_REVISION - << ", distributed by: " << DISTRIBUTOR; - - if (state.log.verbosity > 1) { - qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR; - qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion(); - qCInfo(logBare).noquote() << "Compiler:" << COMPILER; - qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS; - } - - if (state.log.verbosity > 0) { - qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE; - qCInfo(logBare).noquote() << "Build configuration:"; - qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION; - } - } else if (*state.subcommand.log) { - return readLogFile(state); - } else if (*state.subcommand.list) { - return listInstances(state); - } else if (*state.subcommand.kill) { - return killInstances(state); - } else if (*state.subcommand.msg) { - return msgInstance(state); - } else { - if (strcmp(qVersion(), QT_VERSION_STR) != 0) { - qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR - << "but the system has updated to Qt" << qVersion() - << "without rebuilding the package. This is likely to cause crashes, so " - "the quickshell package must be rebuilt.\n"; - } - - return launchFromCommand(state, coreApplication); - } - - return 0; -} - -int locateConfigFile(CommandState& cmd, QString& path) { - if (!cmd.config.path->isEmpty()) { - path = *cmd.config.path; - } else { - auto manifestPath = *cmd.config.manifest; - if (manifestPath.isEmpty()) { - auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); - auto path = configDir.filePath("manifest.conf"); - if (QFileInfo(path).isFile()) manifestPath = path; - } - - if (!manifestPath.isEmpty()) { - auto file = QFile(manifestPath); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - auto stream = QTextStream(&file); - while (!stream.atEnd()) { - auto line = stream.readLine(); - if (line.trimmed().startsWith("#")) continue; - if (line.trimmed().isEmpty()) continue; - - auto split = line.split('='); - if (split.length() != 2) { - qCritical() << "Manifest line not in expected format 'name = relativepath':" << line; - return -1; - } - - if (split[0].trimmed() == *cmd.config.name) { - path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed()); - break; - } - } - - if (path.isEmpty()) { - qCCritical(logBare) << "Configuration" << *cmd.config.name - << "not found when searching manifest" << manifestPath; - return -1; - } - } else { - qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest; - return -1; - } - } else { - auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); - - if (cmd.config.name->isEmpty()) { - path = configDir.path(); - } else { - path = configDir.filePath(*cmd.config.name); - } - } - } - - if (QFileInfo(path).isDir()) { - path = QDir(path).filePath("shell.qml"); - } - - if (!QFileInfo(path).isFile()) { - qCCritical(logBare) << "Could not open config file at" << path; - return -1; - } - - path = QFileInfo(path).canonicalFilePath(); - - return 0; -} - -void sortInstances(QVector<InstanceLockInfo>& list) { - std::sort(list.begin(), list.end(), [](const InstanceLockInfo& a, const InstanceLockInfo& b) { - return a.instance.launchTime < b.instance.launchTime; - }); -}; - -int selectInstance(CommandState& cmd, InstanceLockInfo* instance) { - auto* basePath = QsPaths::instance()->baseRunDir(); - if (!basePath) return -1; - - QString path; - - if (cmd.instance.pid != -1) { - path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid)); - if (!QsPaths::checkLock(path, instance)) { - qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid; - return -1; - } - } else if (!cmd.instance.id->isEmpty()) { - path = basePath->filePath("by-pid"); - auto instances = QsPaths::collectInstances(path); - - auto itr = - std::remove_if(instances.begin(), instances.end(), [&](const InstanceLockInfo& info) { - return !info.instance.instanceId.startsWith(*cmd.instance.id); - }); - - instances.erase(itr, instances.end()); - - if (instances.isEmpty()) { - qCInfo(logBare) << "No running instances start with" << *cmd.instance.id; - return -1; - } else if (instances.length() != 1) { - qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id; - - for (auto& instance: instances) { - qCInfo(logBare).noquote() << " -" << instance.instance.instanceId; - } - - return -1; - } else { - *instance = instances.value(0); - } - } else { - QString configFilePath; - auto r = locateConfigFile(cmd, configFilePath); - if (r != 0) return r; - - auto pathId = - QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex(); - - path = QDir(basePath->filePath("by-path")).filePath(pathId); - - auto instances = QsPaths::collectInstances(path); - sortInstances(instances); - - if (instances.isEmpty()) { - qCInfo(logBare) << "No running instances for" << configFilePath; - return -1; - } - - *instance = instances.value(0); - } - - return 0; -} - -int readLogFile(CommandState& cmd) { - auto path = *cmd.log.file; - - if (path.isEmpty()) { - InstanceLockInfo instance; - auto r = selectInstance(cmd, &instance); - if (r != 0) return r; - - path = QDir(QsPaths::basePath(instance.instance.instanceId)).filePath("log.qslog"); - } - - auto file = QFile(path); - if (!file.open(QFile::ReadOnly)) { - qCCritical(logBare) << "Failed to open log file" << path; - return -1; - } - - return qs::log::readEncodedLogs( - &file, - path, - cmd.log.timestamp, - cmd.log.tail, - cmd.log.follow, - *cmd.log.readoutRules - ) - ? 0 - : -1; -} - -int listInstances(CommandState& cmd) { - auto* basePath = QsPaths::instance()->baseRunDir(); - if (!basePath) return -1; // NOLINT - - QString path; - QString configFilePath; - if (cmd.instance.all) { - path = basePath->filePath("by-pid"); - } else { - auto r = locateConfigFile(cmd, configFilePath); - - if (r != 0) { - qCInfo(logBare) << "Use --all to list all instances."; - return r; - } - - auto pathId = - QCryptographicHash::hash(configFilePath.toUtf8(), QCryptographicHash::Md5).toHex(); - - path = QDir(basePath->filePath("by-path")).filePath(pathId); - } - - auto instances = QsPaths::collectInstances(path); - - if (instances.isEmpty()) { - if (cmd.instance.all) { - qCInfo(logBare) << "No running instances."; - } else { - qCInfo(logBare) << "No running instances for" << configFilePath; - qCInfo(logBare) << "Use --all to list all instances."; - } - } else { - sortInstances(instances); - - if (cmd.output.json) { - auto array = QJsonArray(); - - for (auto& instance: instances) { - auto json = QJsonObject(); - - json["id"] = instance.instance.instanceId; - json["pid"] = instance.pid; - json["shell_id"] = instance.instance.shellId; - json["config_path"] = instance.instance.configPath; - json["launch_time"] = instance.instance.launchTime.toString(Qt::ISODate); - - array.push_back(json); - } - - auto document = QJsonDocument(array); - QTextStream(stdout) << document.toJson(QJsonDocument::Indented); - } else { - for (auto& instance: instances) { - auto launchTimeStr = instance.instance.launchTime.toString("yyyy-MM-dd hh:mm:ss"); - - auto runSeconds = instance.instance.launchTime.secsTo(QDateTime::currentDateTime()); - auto remSeconds = runSeconds % 60; - auto runMinutes = (runSeconds - remSeconds) / 60; - auto remMinutes = runMinutes % 60; - auto runHours = (runMinutes - remMinutes) / 60; - auto runtimeStr = QString("%1 hours, %2 minutes, %3 seconds") - .arg(runHours) - .arg(remMinutes) - .arg(remSeconds); - - qCInfo(logBare).noquote().nospace() - << "Instance " << instance.instance.instanceId << ":\n" - << " Process ID: " << instance.pid << '\n' - << " Shell ID: " << instance.instance.shellId << '\n' - << " Config path: " << instance.instance.configPath << '\n' - << " Launch time: " << launchTimeStr << " (running for " << runtimeStr << ")\n"; - } - } - } - - return 0; -} - -int killInstances(CommandState& cmd) { - InstanceLockInfo instance; - auto r = selectInstance(cmd, &instance); - if (r != 0) return r; - - return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) { - client.kill(); - qCInfo(logBare).noquote() << "Killed" << instance.instance.instanceId; - }); -} - -int msgInstance(CommandState& cmd) { - InstanceLockInfo instance; - auto r = selectInstance(cmd, &instance); - if (r != 0) return r; - - return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) { - if (cmd.ipc.info) { - return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.function); - } else { - QVector<QString> arguments; - for (auto& arg: cmd.ipc.arguments) { - arguments += *arg; - } - - return qs::io::ipc::comm::callFunction( - &client, - *cmd.ipc.target, - *cmd.ipc.function, - arguments - ); - } - - return -1; - }); -} - -template <typename T> -QString base36Encode(T number) { - const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz"; - QString result; - - do { - result.prepend(digits[number % 36]); - number /= 36; - } while (number > 0); - - for (auto i = 0; i < result.length() / 2; i++) { - auto opposite = result.length() - i - 1; - auto c = result.at(i); - result[i] = result.at(opposite); - result[opposite] = c; - } - - return result; -} - -void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) { -#if CRASH_REPORTER - auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD"); - - if (!lastInfoFdStr.isEmpty()) { - auto lastInfoFd = lastInfoFdStr.toInt(); - - QFile file; - file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle); - file.seek(0); - - auto ds = QDataStream(&file); - RelaunchInfo info; - ds >> info; - - LogManager::init( - !info.noColor, - info.timestamp, - info.sparseLogsOnly, - info.defaultLogLevel, - info.logRules - ); - - qCritical().nospace() << "Quickshell has crashed under pid " - << qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt() - << " (Coredumps will be available under that pid.)"; - - qCritical() << "Further crash information is stored under" - << QsPaths::crashDir(info.instance.instanceId).path(); - - if (info.instance.launchTime.msecsTo(QDateTime::currentDateTime()) < 10000) { - qCritical() << "Quickshell crashed within 10 seconds of launching. Not restarting to avoid " - "a crash loop."; - exit(-1); // NOLINT - } else { - qCritical() << "Quickshell has been restarted."; - - launch({.configPath = info.instance.configPath}, argv, coreApplication); - } - } -#endif -} - -int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) { - QString configPath; - - auto r = locateConfigFile(cmd, configPath); - if (r != 0) return r; - - { - InstanceLockInfo info; - if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) { - qCInfo(logBare) << "An instance of this configuration is already running."; - return 0; - } - } - - return launch( - { - .configPath = configPath, - .debugPort = cmd.debug.port, - .waitForDebug = cmd.debug.wait, - }, - cmd.exec.argv, - coreApplication - ); -} - -int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) { - auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex(); - auto shellId = QString(pathId); - - qInfo() << "Launching config:" << args.configPath; - - auto file = QFile(args.configPath); - if (!file.open(QFile::ReadOnly | QFile::Text)) { - qCritical() << "Could not open config file" << args.configPath; - return -1; - } - - struct { - bool useQApplication = false; - bool nativeTextRendering = false; - bool desktopSettingsAware = true; - QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); - QHash<QString, QString> envOverrides; - } pragmas; - - auto stream = QTextStream(&file); - while (!stream.atEnd()) { - auto line = stream.readLine().trimmed(); - if (line.startsWith("//@ pragma ")) { - auto pragma = line.sliced(11).trimmed(); - - if (pragma == "UseQApplication") pragmas.useQApplication = true; - else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true; - else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false; - else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10); - else if (pragma.startsWith("Env ")) { - auto envPragma = pragma.sliced(4); - auto splitIdx = envPragma.indexOf('='); - - if (splitIdx == -1) { - qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'"; - return -1; - } - - auto var = envPragma.sliced(0, splitIdx).trimmed(); - auto val = envPragma.sliced(splitIdx + 1).trimmed(); - pragmas.envOverrides.insert(var, val); - } else if (pragma.startsWith("ShellId ")) { - shellId = pragma.sliced(8).trimmed(); - } else { - qCritical() << "Unrecognized pragma" << pragma; - return -1; - } - } else if (line.startsWith("import")) break; - } - - file.close(); - - if (!pragmas.iconTheme.isEmpty()) { - QIcon::setThemeName(pragmas.iconTheme); - } - - qInfo() << "Shell ID:" << shellId << "Path ID" << pathId; - - auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch(); - InstanceInfo::CURRENT = InstanceInfo { - .instanceId = base36Encode(getpid()) + base36Encode(launchTime), - .configPath = args.configPath, - .shellId = shellId, - .launchTime = qs::Common::LAUNCH_TIME, - }; - -#if CRASH_REPORTER - auto crashHandler = crash::CrashHandler(); - crashHandler.init(); - - { - auto* log = LogManager::instance(); - crashHandler.setRelaunchInfo({ - .instance = InstanceInfo::CURRENT, - .noColor = !log->colorLogs, - .timestamp = log->timestampLogs, - .sparseLogsOnly = log->isSparse(), - .defaultLogLevel = log->defaultLevel(), - .logRules = log->rulesString(), - }); - } -#endif - - QsPaths::init(shellId, pathId); - QsPaths::instance()->linkRunDir(); - QsPaths::instance()->linkPathDir(); - LogManager::initFs(); - - for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) { - qputenv(var.toUtf8(), val.toUtf8()); - } - - // The qml engine currently refuses to cache non file (qsintercept) paths. - - // if (auto* cacheDir = QsPaths::instance()->cacheDir()) { - // auto qmlCacheDir = cacheDir->filePath("qml-engine-cache"); - // qputenv("QML_DISK_CACHE_PATH", qmlCacheDir.toLocal8Bit()); - // - // if (!qEnvironmentVariableIsSet("QML_DISK_CACHE")) { - // qputenv("QML_DISK_CACHE", "aot,qmlc"); - // } - // } - - // While the simple animation driver can lead to better animations in some cases, - // it also can cause excessive repainting at excessively high framerates which can - // lead to noticeable amounts of gpu usage, including overheating on some systems. - // This gets worse the more windows are open, as repaints trigger on all of them for - // some reason. See QTBUG-126099 for details. - - // if (!qEnvironmentVariableIsSet("QSG_USE_SIMPLE_ANIMATION_DRIVER")) { - // qputenv("QSG_USE_SIMPLE_ANIMATION_DRIVER", "1"); - // } - - // Some programs place icons in the pixmaps folder instead of the icons folder. - // This seems to be controlled by the QPA and qt6ct does not provide it. - { - QList<QString> dataPaths; - - if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { - auto var = qEnvironmentVariable("XDG_DATA_DIRS"); - dataPaths = var.split(u':', Qt::SkipEmptyParts); - } else { - dataPaths.push_back("/usr/local/share"); - dataPaths.push_back("/usr/share"); - } - - auto fallbackPaths = QIcon::fallbackSearchPaths(); - - for (auto& path: dataPaths) { - auto newPath = QDir(path).filePath("pixmaps"); - - if (!fallbackPaths.contains(newPath)) { - fallbackPaths.push_back(newPath); - } - } - - QIcon::setFallbackSearchPaths(fallbackPaths); - } - - QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware); - - delete coreApplication; - - QGuiApplication* app = nullptr; - auto qArgC = 0; - - if (pragmas.useQApplication) { - app = new QApplication(qArgC, argv); - } else { - app = new QGuiApplication(qArgC, argv); - } - - if (args.debugPort != -1) { - QQmlDebuggingEnabler::enableDebugging(true); - auto wait = args.waitForDebug ? QQmlDebuggingEnabler::WaitForClient - : QQmlDebuggingEnabler::DoNotWaitForClient; - QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait); - } - - QuickshellPlugin::initPlugins(); - - // Base window transparency appears to be additive. - // Use a fully transparent window with a colored rect. - QQuickWindow::setDefaultAlphaBuffer(true); - - if (pragmas.nativeTextRendering) { - QQuickWindow::setTextRenderType(QQuickWindow::NativeTextRendering); - } - - qs::ipc::IpcServer::start(); - QsPaths::instance()->createLock(); - - auto root = RootWrapper(args.configPath, shellId); - QGuiApplication::setQuitOnLastWindowClosed(false); - - exitDaemon(0); - - auto code = QGuiApplication::exec(); - delete app; - return code; -} - -} // namespace qs::launch +#include "launch/main.hpp" int main(int argc, char** argv) { return qs::launch::main(argc, argv); } diff --git a/src/services/greetd/CMakeLists.txt b/src/services/greetd/CMakeLists.txt index 870f8085..2252f8cf 100644 --- a/src/services/greetd/CMakeLists.txt +++ b/src/services/greetd/CMakeLists.txt @@ -11,9 +11,9 @@ qt_add_qml_module(quickshell-service-greetd install_qml_module(quickshell-service-greetd) -target_link_libraries(quickshell-service-greetd PRIVATE ${QT_DEPS}) +# can't be Qt::Qml because generation.hpp pulls in gui types +target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick) -qs_pch(quickshell-service-greetd) -qs_pch(quickshell-service-greetdplugin) +qs_module_pch(quickshell-service-greetd) target_link_libraries(quickshell PRIVATE quickshell-service-greetdplugin) diff --git a/src/services/mpris/CMakeLists.txt b/src/services/mpris/CMakeLists.txt index 505df7a6..122a0c5c 100644 --- a/src/services/mpris/CMakeLists.txt +++ b/src/services/mpris/CMakeLists.txt @@ -30,13 +30,15 @@ target_include_directories(quickshell-service-mpris PRIVATE ${CMAKE_CURRENT_BINA qt_add_qml_module(quickshell-service-mpris URI Quickshell.Services.Mpris VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-service-mpris Quickshell) + install_qml_module(quickshell-service-mpris) -target_link_libraries(quickshell-service-mpris PRIVATE ${QT_DEPS} quickshell-dbus) -target_link_libraries(quickshell PRIVATE quickshell-service-mprisplugin) +target_link_libraries(quickshell-service-mpris PRIVATE Qt::Qml Qt::DBus) -qs_pch(quickshell-service-mpris) -qs_pch(quickshell-service-mprisplugin) +qs_module_pch(quickshell-service-mpris SET dbus) + +target_link_libraries(quickshell PRIVATE quickshell-service-mprisplugin) diff --git a/src/services/notifications/CMakeLists.txt b/src/services/notifications/CMakeLists.txt index 4ba8d3cc..0cbb42eb 100644 --- a/src/services/notifications/CMakeLists.txt +++ b/src/services/notifications/CMakeLists.txt @@ -20,13 +20,13 @@ target_include_directories(quickshell-service-notifications PRIVATE ${CMAKE_CURR qt_add_qml_module(quickshell-service-notifications URI Quickshell.Services.Notifications VERSION 0.1 - DEPENDENCIES QtQml Quickshell ) +qs_add_module_deps_light(quickshell-service-notifications Quickshell) + install_qml_module(quickshell-service-notifications) -target_link_libraries(quickshell-service-notifications PRIVATE ${QT_DEPS} quickshell-dbus) +target_link_libraries(quickshell-service-notifications PRIVATE Qt::Quick Qt::DBus) target_link_libraries(quickshell PRIVATE quickshell-service-notificationsplugin) -qs_pch(quickshell-service-notifications) -qs_pch(quickshell-service-notificationsplugin) +qs_module_pch(quickshell-service-notifications SET dbus) diff --git a/src/services/pam/CMakeLists.txt b/src/services/pam/CMakeLists.txt index f9d017e7..c35e74af 100644 --- a/src/services/pam/CMakeLists.txt +++ b/src/services/pam/CMakeLists.txt @@ -13,9 +13,8 @@ qt_add_qml_module(quickshell-service-pam install_qml_module(quickshell-service-pam) -target_link_libraries(quickshell-service-pam PRIVATE ${QT_DEPS} pam ${PAM_LIBRARIES}) +target_link_libraries(quickshell-service-pam PRIVATE Qt::Qml pam ${PAM_LIBRARIES}) -qs_pch(quickshell-service-pam) -qs_pch(quickshell-service-pamplugin) +qs_module_pch(quickshell-service-pam) target_link_libraries(quickshell PRIVATE quickshell-service-pamplugin) diff --git a/src/services/pipewire/CMakeLists.txt b/src/services/pipewire/CMakeLists.txt index bb74a078..35aaa137 100644 --- a/src/services/pipewire/CMakeLists.txt +++ b/src/services/pipewire/CMakeLists.txt @@ -16,14 +16,17 @@ qt_add_library(quickshell-service-pipewire STATIC qt_add_qml_module(quickshell-service-pipewire URI Quickshell.Services.Pipewire VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-service-pipewire Quickshell) + install_qml_module(quickshell-service-pipewire) -target_link_libraries(quickshell-service-pipewire PRIVATE ${QT_DEPS} PkgConfig::pipewire) +target_link_libraries(quickshell-service-pipewire PRIVATE + Qt::Qml PkgConfig::pipewire +) -qs_pch(quickshell-service-pipewire) -qs_pch(quickshell-service-pipewireplugin) +qs_module_pch(quickshell-service-pipewire) target_link_libraries(quickshell PRIVATE quickshell-service-pipewireplugin) diff --git a/src/services/status_notifier/CMakeLists.txt b/src/services/status_notifier/CMakeLists.txt index 20de11a1..7e0bf2b9 100644 --- a/src/services/status_notifier/CMakeLists.txt +++ b/src/services/status_notifier/CMakeLists.txt @@ -41,13 +41,14 @@ target_include_directories(quickshell-service-statusnotifier PRIVATE ${CMAKE_CUR qt_add_qml_module(quickshell-service-statusnotifier URI Quickshell.Services.SystemTray VERSION 0.1 - DEPENDENCIES QtQml Quickshell Quickshell.DBusMenu + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-service-statusnotifier Quickshell Quickshell.DBusMenu) + install_qml_module(quickshell-service-statusnotifier) -target_link_libraries(quickshell-service-statusnotifier PRIVATE ${QT_DEPS} quickshell-dbus quickshell-dbusmenuplugin) +target_link_libraries(quickshell-service-statusnotifier PRIVATE Qt::Quick Qt::DBus) target_link_libraries(quickshell PRIVATE quickshell-service-statusnotifierplugin) -qs_pch(quickshell-service-statusnotifier) -qs_pch(quickshell-service-statusnotifierplugin) +qs_module_pch(quickshell-service-statusnotifier SET dbus) diff --git a/src/services/upower/CMakeLists.txt b/src/services/upower/CMakeLists.txt index e913a550..fd0da2af 100644 --- a/src/services/upower/CMakeLists.txt +++ b/src/services/upower/CMakeLists.txt @@ -30,13 +30,14 @@ target_include_directories(quickshell-service-upower PRIVATE ${CMAKE_CURRENT_BIN qt_add_qml_module(quickshell-service-upower URI Quickshell.Services.UPower VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-service-upower Quickshell) + install_qml_module(quickshell-service-upower) -target_link_libraries(quickshell-service-upower PRIVATE ${QT_DEPS} quickshell-dbus) +target_link_libraries(quickshell-service-upower PRIVATE Qt::Qml Qt::DBus quickshell-dbus) target_link_libraries(quickshell PRIVATE quickshell-service-upowerplugin) -qs_pch(quickshell-service-upower) -qs_pch(quickshell-service-upowerplugin) +qs_module_pch(quickshell-service-upower SET dbus) diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 19e74b90..8005a833 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -20,6 +20,14 @@ execute_process( message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") +qs_add_pchset(wayland-protocol + DEPENDENCIES Qt::Core Qt::WaylandClient Qt::WaylandClientPrivate + HEADERS + <wayland-client.h> + <qbytearray.h> + <qstring.h> +) + function (wl_proto target name path) set(PROTO_BUILD_PATH ${CMAKE_CURRENT_BINARY_DIR}/wl-proto/${name}) make_directory(${PROTO_BUILD_PATH}) @@ -53,13 +61,12 @@ function (wl_proto target name path) DEPENDS Qt6::qtwaylandscanner "${path}" ) - add_library(wl-proto-${name} - ${WS_CLIENT_HEADER} ${WS_CLIENT_CODE} - ${QWS_CLIENT_HEADER} ${QWS_CLIENT_CODE} - ) + add_library(wl-proto-${name}-wl STATIC ${WS_CLIENT_HEADER} ${WS_CLIENT_CODE}) + add_library(wl-proto-${name} STATIC ${QWS_CLIENT_HEADER} ${QWS_CLIENT_CODE}) target_include_directories(wl-proto-${name} INTERFACE ${PROTO_BUILD_PATH}) - target_link_libraries(wl-proto-${name} Qt6::WaylandClient Qt6::WaylandClientPrivate) + target_link_libraries(wl-proto-${name} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate) + qs_pch(wl-proto-${name} SET wayland-protocol) target_link_libraries(${target} PRIVATE wl-proto-${name}) endfunction() @@ -100,20 +107,24 @@ if (HYPRLAND) add_subdirectory(hyprland) endif() -target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS}) -target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS}) +# widgets for qmenu +target_link_libraries(quickshell-wayland PRIVATE + Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate +) + +target_link_libraries(quickshell-wayland-init PRIVATE Qt::Quick) qt_add_qml_module(quickshell-wayland URI Quickshell.Wayland VERSION 0.1 - DEPENDENCIES QtQuick Quickshell + DEPENDENCIES QtQuick IMPORTS ${WAYLAND_MODULES} ) +qs_add_module_deps_light(quickshell-wayland Quickshell) + install_qml_module(quickshell-wayland) -qs_pch(quickshell-wayland) -qs_pch(quickshell-waylandplugin) -qs_pch(quickshell-wayland-init) +qs_module_pch(quickshell-wayland SET large) target_link_libraries(quickshell PRIVATE quickshell-waylandplugin quickshell-wayland-init) diff --git a/src/wayland/hyprland/CMakeLists.txt b/src/wayland/hyprland/CMakeLists.txt index 59458fe6..cb375358 100644 --- a/src/wayland/hyprland/CMakeLists.txt +++ b/src/wayland/hyprland/CMakeLists.txt @@ -27,7 +27,6 @@ qt_add_qml_module(quickshell-hyprland install_qml_module(quickshell-hyprland) -qs_pch(quickshell-hyprland) -qs_pch(quickshell-hyprlandplugin) +# intentionally no pch as the module is empty target_link_libraries(quickshell PRIVATE quickshell-hyprlandplugin) diff --git a/src/wayland/hyprland/focus_grab/CMakeLists.txt b/src/wayland/hyprland/focus_grab/CMakeLists.txt index 0fd1f85e..04b6e0a9 100644 --- a/src/wayland/hyprland/focus_grab/CMakeLists.txt +++ b/src/wayland/hyprland/focus_grab/CMakeLists.txt @@ -7,9 +7,11 @@ qt_add_library(quickshell-hyprland-focus-grab STATIC qt_add_qml_module(quickshell-hyprland-focus-grab URI Quickshell.Hyprland._FocusGrab VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQml ) +qs_add_module_deps_light(quickshell-hyprland-focus-grab Quickshell) + install_qml_module(quickshell-hyprland-focus-grab) wl_proto(quickshell-hyprland-focus-grab @@ -17,9 +19,10 @@ wl_proto(quickshell-hyprland-focus-grab "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-focus-grab-v1.xml" ) -target_link_libraries(quickshell-hyprland-focus-grab PRIVATE ${QT_DEPS} wayland-client) +target_link_libraries(quickshell-hyprland-focus-grab PRIVATE + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client +) -qs_pch(quickshell-hyprland-focus-grab) -qs_pch(quickshell-hyprland-focus-grabplugin) +qs_module_pch(quickshell-hyprland-focus-grab SET large) target_link_libraries(quickshell PRIVATE quickshell-hyprland-focus-grabplugin) diff --git a/src/wayland/hyprland/global_shortcuts/CMakeLists.txt b/src/wayland/hyprland/global_shortcuts/CMakeLists.txt index d2314177..8b2aa94f 100644 --- a/src/wayland/hyprland/global_shortcuts/CMakeLists.txt +++ b/src/wayland/hyprland/global_shortcuts/CMakeLists.txt @@ -17,9 +17,10 @@ wl_proto(quickshell-hyprland-global-shortcuts "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-global-shortcuts-v1.xml" ) -target_link_libraries(quickshell-hyprland-global-shortcuts PRIVATE ${QT_DEPS} wayland-client) +target_link_libraries(quickshell-hyprland-global-shortcuts PRIVATE + Qt::Qml Qt::WaylandClient Qt::WaylandClientPrivate wayland-client +) -qs_pch(quickshell-hyprland-global-shortcuts) -qs_pch(quickshell-hyprland-global-shortcutsplugin) +qs_module_pch(quickshell-hyprland-global-shortcuts) target_link_libraries(quickshell PRIVATE quickshell-hyprland-global-shortcutsplugin) diff --git a/src/wayland/hyprland/ipc/CMakeLists.txt b/src/wayland/hyprland/ipc/CMakeLists.txt index 367fa8f4..fd1da674 100644 --- a/src/wayland/hyprland/ipc/CMakeLists.txt +++ b/src/wayland/hyprland/ipc/CMakeLists.txt @@ -8,14 +8,15 @@ qt_add_library(quickshell-hyprland-ipc STATIC qt_add_qml_module(quickshell-hyprland-ipc URI Quickshell.Hyprland._Ipc VERSION 0.1 - DEPENDENCIES QtQml Quickshell + DEPENDENCIES QtQuick ) +qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell) + install_qml_module(quickshell-hyprland-ipc) -target_link_libraries(quickshell-hyprland-ipc PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick) -qs_pch(quickshell-hyprland-ipc) -qs_pch(quickshell-hyprland-ipcplugin) +qs_module_pch(quickshell-hyprland-ipc SET large) target_link_libraries(quickshell PRIVATE quickshell-hyprland-ipcplugin) diff --git a/src/wayland/platformmenu.cpp b/src/wayland/platformmenu.cpp index 80f9854e..e64e8880 100644 --- a/src/wayland/platformmenu.cpp +++ b/src/wayland/platformmenu.cpp @@ -8,6 +8,7 @@ #include <qwindow.h> #include "../core/platformmenu.hpp" +#include "../core/platformmenu_p.hpp" using namespace qs::menu::platform; diff --git a/src/wayland/session_lock/CMakeLists.txt b/src/wayland/session_lock/CMakeLists.txt index d6224a8b..63dc1295 100644 --- a/src/wayland/session_lock/CMakeLists.txt +++ b/src/wayland/session_lock/CMakeLists.txt @@ -8,6 +8,7 @@ qt_add_library(quickshell-wayland-sessionlock STATIC wl_proto(quickshell-wayland-sessionlock ext-session-lock-v1 "${WAYLAND_PROTOCOLS}/staging/ext-session-lock/ext-session-lock-v1.xml") target_link_libraries(quickshell-wayland-sessionlock PRIVATE ${QT_DEPS} wayland-client) -qs_pch(quickshell-wayland-sessionlock) + +qs_pch(quickshell-wayland-sessionlock SET large) target_link_libraries(quickshell-wayland PRIVATE quickshell-wayland-sessionlock) diff --git a/src/wayland/toplevel_management/CMakeLists.txt b/src/wayland/toplevel_management/CMakeLists.txt index 01c9d756..0db82aae 100644 --- a/src/wayland/toplevel_management/CMakeLists.txt +++ b/src/wayland/toplevel_management/CMakeLists.txt @@ -7,7 +7,11 @@ qt_add_library(quickshell-wayland-toplevel-management STATIC qt_add_qml_module(quickshell-wayland-toplevel-management URI Quickshell.Wayland._ToplevelManagement VERSION 0.1 - DEPENDENCIES QtQml Quickshell Quickshell.Wayland + DEPENDENCIES QtQml +) + +qs_add_module_deps_light(quickshell-wayland-toplevel-management + Quickshell Quickshell.Wayland ) install_qml_module(quickshell-wayland-toplevel-management) @@ -17,9 +21,10 @@ wl_proto(quickshell-wayland-toplevel-management "${CMAKE_CURRENT_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1.xml" ) -target_link_libraries(quickshell-wayland-toplevel-management PRIVATE ${QT_DEPS} wayland-client) +target_link_libraries(quickshell-wayland-toplevel-management PRIVATE + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client +) -qs_pch(quickshell-wayland-toplevel-management) -qs_pch(quickshell-wayland-toplevel-managementplugin) +qs_module_pch(quickshell-wayland-toplevel-management SET large) target_link_libraries(quickshell PRIVATE quickshell-wayland-toplevel-managementplugin) diff --git a/src/wayland/wlr_layershell/CMakeLists.txt b/src/wayland/wlr_layershell/CMakeLists.txt index 640b7ec2..11bedc6a 100644 --- a/src/wayland/wlr_layershell/CMakeLists.txt +++ b/src/wayland/wlr_layershell/CMakeLists.txt @@ -7,17 +7,19 @@ qt_add_library(quickshell-wayland-layershell STATIC qt_add_qml_module(quickshell-wayland-layershell URI Quickshell.Wayland._WlrLayerShell VERSION 0.1 - # Quickshell.Wayland currently creates a dependency cycle, add it here once the main - # ls class is moved to this module. - DEPENDENCIES QtQuick Quickshell + DEPENDENCIES QtQuick ) +qs_add_module_deps_light(quickshell-wayland-layershell Quickshell Quickshell.Wayland) + install_qml_module(quickshell-wayland-layershell) wl_proto(quickshell-wayland-layershell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/wlr-layer-shell-unstable-v1.xml") -target_link_libraries(quickshell-wayland-layershell PRIVATE ${QT_DEPS} wayland-client) -qs_pch(quickshell-wayland-layershell) -qs_pch(quickshell-wayland-layershellplugin) +target_link_libraries(quickshell-wayland-layershell PRIVATE + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client +) + +qs_module_pch(quickshell-wayland-layershell SET large) target_link_libraries(quickshell-wayland PRIVATE quickshell-wayland-layershellplugin) diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index 06671b13..226d950d 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -9,7 +9,6 @@ qt_add_qml_module(quickshell-widgets install_qml_module(quickshell-widgets) -qs_pch(quickshell-widgets) -qs_pch(quickshell-widgetsplugin) +qs_module_pch(quickshell-widgets) target_link_libraries(quickshell PRIVATE quickshell-widgetsplugin) diff --git a/src/window/CMakeLists.txt b/src/window/CMakeLists.txt index e7dd1977..89b2233e 100644 --- a/src/window/CMakeLists.txt +++ b/src/window/CMakeLists.txt @@ -9,20 +9,22 @@ qt_add_library(quickshell-window STATIC qt_add_qml_module(quickshell-window URI Quickshell._Window VERSION 0.1 - DEPENDENCIES QtQuick Quickshell + DEPENDENCIES QtQuick ) +qs_add_module_deps_light(quickshell-window Quickshell) + install_qml_module(quickshell-window) add_library(quickshell-window-init OBJECT init.cpp) -target_link_libraries(quickshell-window PRIVATE ${QT_DEPS} Qt6::QuickPrivate) -target_link_libraries(quickshell-windowplugin PRIVATE ${QT_DEPS}) -target_link_libraries(quickshell-window-init PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-window PRIVATE + Qt::Core Qt::Gui Qt::Quick Qt6::QuickPrivate +) -qs_pch(quickshell-window) -qs_pch(quickshell-windowplugin) -qs_pch(quickshell-window-init) +target_link_libraries(quickshell-window-init PRIVATE Qt::Qml) + +qs_module_pch(quickshell-window SET large) target_link_libraries(quickshell PRIVATE quickshell-windowplugin quickshell-window-init) diff --git a/src/window/init.cpp b/src/window/init.cpp index ef2b8c1d..9930b41b 100644 --- a/src/window/init.cpp +++ b/src/window/init.cpp @@ -1,3 +1,6 @@ +#include <qqml.h> +#include <qstring.h> + #include "../core/plugin.hpp" namespace { diff --git a/src/window/test/CMakeLists.txt b/src/window/test/CMakeLists.txt index ad9e5a0a..4197e4a5 100644 --- a/src/window/test/CMakeLists.txt +++ b/src/window/test/CMakeLists.txt @@ -1,6 +1,6 @@ function (qs_test name) add_executable(${name} ${ARGN}) - target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test quickshell-window quickshell-core) + target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core) add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>) endfunction() diff --git a/src/x11/CMakeLists.txt b/src/x11/CMakeLists.txt index d1079d29..b37b8fbc 100644 --- a/src/x11/CMakeLists.txt +++ b/src/x11/CMakeLists.txt @@ -8,17 +8,16 @@ qt_add_library(quickshell-x11 STATIC qt_add_qml_module(quickshell-x11 URI Quickshell.X11 VERSION 0.1 + DEPENDENCIES QtQuick ) install_qml_module(quickshell-x11) add_library(quickshell-x11-init OBJECT init.cpp) -target_link_libraries(quickshell-x11 PRIVATE ${QT_DEPS} ${XCB_LIBRARIES}) -target_link_libraries(quickshell-x11-init PRIVATE ${QT_DEPS} ${XCB_LIBRARIES}) +target_link_libraries(quickshell-x11 PRIVATE Qt::Quick ${XCB_LIBRARIES}) +target_link_libraries(quickshell-x11-init PRIVATE Qt::Quick Qt::Qml ${XCB_LIBRARIES}) -qs_pch(quickshell-x11) -qs_pch(quickshell-x11plugin) -qs_pch(quickshell-x11-init) +qs_module_pch(quickshell-x11 SET large) target_link_libraries(quickshell PRIVATE quickshell-x11plugin quickshell-x11-init) diff --git a/src/x11/init.cpp b/src/x11/init.cpp index 00080036..2e41e761 100644 --- a/src/x11/init.cpp +++ b/src/x11/init.cpp @@ -1,5 +1,7 @@ #include <qguiapplication.h> +#include <qlist.h> #include <qqml.h> +#include <qstring.h> #include "../core/plugin.hpp" #include "panel_window.hpp" @@ -8,6 +10,8 @@ namespace { class X11Plugin: public QuickshellPlugin { + QList<QString> dependencies() override { return {"window"}; } + bool applies() override { return QGuiApplication::platformName() == "xcb"; } void init() override { XAtom::initAtoms(); }