diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml index 80fa827..958c884 100644 --- a/.github/ISSUE_TEMPLATE/crash.yml +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -1,82 +1,17 @@ name: Crash Report (v1) -description: Quickshell has crashed -labels: ["bug", "crash"] +description: Quickshell has crashed (old) +labels: ["unactionable"] body: - - type: textarea - id: crashinfo + - type: markdown attributes: - label: General crash information - description: | - Paste the contents of the `info.txt` file in your crash folder here. - value: "
General information - - - ``` - - - - ``` - - -
" - validations: - required: true - - type: textarea - id: userinfo + value: | + Thank you for taking the time to click the report button. + At this point most of the worst issues in 0.2.1 and before have been fixed and we are + preparing for a new release. Please do not submit this report. + - type: checkboxes + id: donotcheck attributes: - label: What caused the crash - description: | - Any information likely to help debug the crash. What were you doing when the crash occurred, - what changes did you make, can you get it to happen again? - - type: textarea - id: dump - attributes: - label: Minidump - description: | - Attach `minidump.dmp.log` here. If it is too big to upload, compress it. - - You may skip this step if quickshell crashed while processing a password - or other sensitive information. If you skipped it write why instead. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Log file - description: | - Attach `log.qslog.log` here. If it is too big to upload, compress it. - - You can preview the log if you'd like using `quickshell read-log `. - validations: - required: true - - type: textarea - id: config - attributes: - label: Configuration - description: | - Attach your configuration here, preferrably in full (not just one file). - Compress it into a zip, tar, etc. - - This will help us reproduce the crash ourselves. - - type: textarea - id: bt - attributes: - label: Backtrace - description: | - If you have gdb installed and use systemd, or otherwise know how to get a backtrace, - we would appreciate one. (You may have gdb installed without knowing it) - - 1. Run `coredumpctl debug ` where `pid` is the number shown after "Crashed process ID" - in the crash reporter. - 2. Once it loads, type `bt -full` (then enter) - 3. Copy the output and attach it as a file or in a spoiler. - - type: textarea - id: exe - attributes: - label: Executable - description: | - If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field. - If it is too big to upload, compress it. - - Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on - filetypes. + label: Read the text above. Do not submit the report. + options: + - label: Yes I want this report to be deleted. + required: true diff --git a/.github/ISSUE_TEMPLATE/crash2.yml b/.github/ISSUE_TEMPLATE/crash2.yml index 84beef8..86f490c 100644 --- a/.github/ISSUE_TEMPLATE/crash2.yml +++ b/.github/ISSUE_TEMPLATE/crash2.yml @@ -9,21 +9,21 @@ body: description: | Any information likely to help debug the crash. What were you doing when the crash occurred, what changes did you make, can you get it to happen again? - - type: textarea + - type: upload id: report attributes: label: Report file description: Attach `report.txt` here. validations: required: true - - type: textarea + - type: upload id: logs attributes: label: Log file description: | Attach `log.qslog.log` here. If it is too big to upload, compress it. - You can preview the log if you'd like using `quickshell read-log `. + You can preview the log if you'd like using `qs log -r '*=true'`. validations: required: true - type: textarea @@ -31,7 +31,7 @@ body: attributes: label: Configuration description: | - Attach your configuration here, preferrably in full (not just one file). + Attach or link your configuration here, preferrably in full (not just one file). Compress it into a zip, tar, etc. This will help us reproduce the crash ourselves. diff --git a/CMakeLists.txt b/CMakeLists.txt index 1226342..4ed8374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,8 +87,9 @@ include(cmake/util.cmake) add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension) -# pipewire defines this, breaking PCH +# pipewire defines these, breaking PCH add_compile_definitions(_REENTRANT) +add_compile_options(-fno-strict-overflow) if (FRAME_POINTERS) add_compile_options(-fno-omit-frame-pointer) diff --git a/changelog/next.md b/changelog/next.md index cceb79e..fc6d79e 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -27,6 +27,8 @@ set shell id. - Added a way to detect if an icon is from the system icon theme or not. - Added vulkan support to screencopy. - Added generic WindowManager interface implementing ext-workspace. +- Added ext-background-effect window blur support. +- Added per-corner radius support to Region. ## Other Changes @@ -38,6 +40,7 @@ set shell id. - Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching. - Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling. - Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link. +- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID. ## Bug Fixes @@ -60,6 +63,7 @@ set shell id. - Desktop action order is now preserved. - Fixed partial socket reads in greetd and hyprland on slow machines. - Worked around Qt bug causing crashes when plugging and unplugging monitors. +- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it. ## Packaging Changes diff --git a/src/core/debuginfo.cpp b/src/core/debuginfo.cpp index ae227f8..abc467d 100644 --- a/src/core/debuginfo.cpp +++ b/src/core/debuginfo.cpp @@ -129,12 +129,13 @@ QString envInfo() { auto stream = QTextStream(&info); for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT - auto prefixes = std::array { + auto prefixes = std::array { "QS_", "QT_", "QML_", "QML2_", "QSG_", + "XDG_CURRENT_DESKTOP=", }; for (const auto& prefix: prefixes) { diff --git a/src/core/instanceinfo.cpp b/src/core/instanceinfo.cpp index 1f71b8a..b9b7b44 100644 --- a/src/core/instanceinfo.cpp +++ b/src/core/instanceinfo.cpp @@ -3,14 +3,14 @@ #include QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { - stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid - << info.display; + stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime + << info.pid << info.display; return stream; } QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { - stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid - >> info.display; + stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime + >> info.pid >> info.display; return stream; } diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp index 977e4c2..a4a7e66 100644 --- a/src/core/instanceinfo.hpp +++ b/src/core/instanceinfo.hpp @@ -9,6 +9,7 @@ struct InstanceInfo { QString instanceId; QString configPath; QString shellId; + QString appId; QDateTime launchTime; pid_t pid = -1; QString display; diff --git a/src/core/logging.cpp b/src/core/logging.cpp index 893c56e..415cf61 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,7 @@ void LogManager::messageHandler( } if (display) { + auto locker = QMutexLocker(&self->stdoutMutex); LogMessage::formatMessage( self->stdoutStream, message, diff --git a/src/core/logging.hpp b/src/core/logging.hpp index bf81133..7b6a758 100644 --- a/src/core/logging.hpp +++ b/src/core/logging.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -135,6 +136,7 @@ private: QHash allFilters; QTextStream stdoutStream; + QMutex stdoutMutex; LoggingThreadProxy threadProxy; friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel); diff --git a/src/core/platformmenu.cpp b/src/core/platformmenu.cpp index 427dde0..d8901e2 100644 --- a/src/core/platformmenu.cpp +++ b/src/core/platformmenu.cpp @@ -18,7 +18,6 @@ #include #include "../window/proxywindow.hpp" -#include "../window/windowinterface.hpp" #include "iconprovider.hpp" #include "model.hpp" #include "platformmenu_p.hpp" @@ -91,10 +90,8 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati } else if (parentWindow == nullptr) { qCritical() << "Cannot display PlatformMenuEntry with null parent window."; return false; - } else if (auto* proxy = qobject_cast(parentWindow)) { + } else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) { window = proxy->backingWindow(); - } else if (auto* interface = qobject_cast(parentWindow)) { - window = interface->proxyWindow()->backingWindow(); } else { qCritical() << "PlatformMenuEntry.display() must be called with a window."; return false; diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 0eb9a06..e6cd1bb 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -9,6 +9,18 @@ static QVector plugins; // NOLINT void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); } +void QsEnginePlugin::preinitPluginsOnly() { + plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); + + std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) { + return b->dependencies().contains(a->name()); + }); + + for (QsEnginePlugin* plugin: plugins) { + plugin->preinit(); + } +} + void QsEnginePlugin::initPlugins() { plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); @@ -16,6 +28,10 @@ void QsEnginePlugin::initPlugins() { return b->dependencies().contains(a->name()); }); + for (QsEnginePlugin* plugin: plugins) { + plugin->preinit(); + } + for (QsEnginePlugin* plugin: plugins) { plugin->init(); } diff --git a/src/core/plugin.hpp b/src/core/plugin.hpp index f0c14dc..f692e91 100644 --- a/src/core/plugin.hpp +++ b/src/core/plugin.hpp @@ -18,12 +18,14 @@ public: virtual QString name() { return QString(); } virtual QList dependencies() { return {}; } virtual bool applies() { return true; } + virtual void preinit() {} virtual void init() {} virtual void registerTypes() {} virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT virtual void onReload() {} static void registerPlugin(QsEnginePlugin& plugin); + static void preinitPluginsOnly(); static void initPlugins(); static void runConstructGeneration(EngineGeneration& generation); static void runOnReload(); diff --git a/src/core/popupanchor.cpp b/src/core/popupanchor.cpp index 151dd5d..ca817c9 100644 --- a/src/core/popupanchor.cpp +++ b/src/core/popupanchor.cpp @@ -11,7 +11,6 @@ #include #include "../window/proxywindow.hpp" -#include "../window/windowinterface.hpp" #include "types.hpp" bool PopupAnchorState::operator==(const PopupAnchorState& other) const { @@ -40,10 +39,8 @@ void PopupAnchor::setWindowInternal(QObject* window) { } if (window) { - if (auto* proxy = qobject_cast(window)) { + if (auto* proxy = ProxyWindowBase::forObject(window)) { this->bProxyWindow = proxy; - } else if (auto* interface = qobject_cast(window)) { - this->bProxyWindow = interface->proxyWindow(); } else { qWarning() << "Tried to set popup anchor window to" << window << "which is not a quickshell window."; diff --git a/src/core/region.cpp b/src/core/region.cpp index 11892d6..82cc2e7 100644 --- a/src/core/region.cpp +++ b/src/core/region.cpp @@ -1,4 +1,5 @@ #include "region.hpp" +#include #include #include @@ -18,6 +19,11 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) { QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::radiusChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::topLeftRadiusChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::topRightRadiusChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::bottomLeftRadiusChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::bottomRightRadiusChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed); } @@ -45,6 +51,79 @@ void PendingRegion::onItemDestroyed() { this->mItem = nullptr; } void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); } +qint32 PendingRegion::radius() const { return this->mRadius; } + +void PendingRegion::setRadius(qint32 radius) { + if (radius == this->mRadius) return; + this->mRadius = radius; + emit this->radiusChanged(); + + if (!(this->mCornerOverrides & TopLeft)) emit this->topLeftRadiusChanged(); + if (!(this->mCornerOverrides & TopRight)) emit this->topRightRadiusChanged(); + if (!(this->mCornerOverrides & BottomLeft)) emit this->bottomLeftRadiusChanged(); + if (!(this->mCornerOverrides & BottomRight)) emit this->bottomRightRadiusChanged(); +} + +qint32 PendingRegion::topLeftRadius() const { + return (this->mCornerOverrides & TopLeft) ? this->mTopLeftRadius : this->mRadius; +} + +void PendingRegion::setTopLeftRadius(qint32 radius) { + this->mTopLeftRadius = radius; + this->mCornerOverrides |= TopLeft; + emit this->topLeftRadiusChanged(); +} + +void PendingRegion::resetTopLeftRadius() { + this->mCornerOverrides &= ~TopLeft; + emit this->topLeftRadiusChanged(); +} + +qint32 PendingRegion::topRightRadius() const { + return (this->mCornerOverrides & TopRight) ? this->mTopRightRadius : this->mRadius; +} + +void PendingRegion::setTopRightRadius(qint32 radius) { + this->mTopRightRadius = radius; + this->mCornerOverrides |= TopRight; + emit this->topRightRadiusChanged(); +} + +void PendingRegion::resetTopRightRadius() { + this->mCornerOverrides &= ~TopRight; + emit this->topRightRadiusChanged(); +} + +qint32 PendingRegion::bottomLeftRadius() const { + return (this->mCornerOverrides & BottomLeft) ? this->mBottomLeftRadius : this->mRadius; +} + +void PendingRegion::setBottomLeftRadius(qint32 radius) { + this->mBottomLeftRadius = radius; + this->mCornerOverrides |= BottomLeft; + emit this->bottomLeftRadiusChanged(); +} + +void PendingRegion::resetBottomLeftRadius() { + this->mCornerOverrides &= ~BottomLeft; + emit this->bottomLeftRadiusChanged(); +} + +qint32 PendingRegion::bottomRightRadius() const { + return (this->mCornerOverrides & BottomRight) ? this->mBottomRightRadius : this->mRadius; +} + +void PendingRegion::setBottomRightRadius(qint32 radius) { + this->mBottomRightRadius = radius; + this->mCornerOverrides |= BottomRight; + emit this->bottomRightRadiusChanged(); +} + +void PendingRegion::resetBottomRightRadius() { + this->mCornerOverrides &= ~BottomRight; + emit this->bottomRightRadiusChanged(); +} + QQmlListProperty PendingRegion::regions() { return QQmlListProperty( this, @@ -90,6 +169,60 @@ QRegion PendingRegion::build() const { region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type); } + if (this->mShape == RegionShape::Rect && !region.isEmpty()) { + auto tl = std::max(this->topLeftRadius(), 0); + auto tr = std::max(this->topRightRadius(), 0); + auto bl = std::max(this->bottomLeftRadius(), 0); + auto br = std::max(this->bottomRightRadius(), 0); + + if (tl > 0 || tr > 0 || bl > 0 || br > 0) { + auto rect = region.boundingRect(); + auto x = rect.x(); + auto y = rect.y(); + auto w = rect.width(); + auto h = rect.height(); + + // Normalize so adjacent corners don't exceed their shared edge. + // Each corner is scaled by the tightest constraint of its two edges. + auto topScale = tl + tr > w ? static_cast(w) / (tl + tr) : 1.0; + auto bottomScale = bl + br > w ? static_cast(w) / (bl + br) : 1.0; + auto leftScale = tl + bl > h ? static_cast(h) / (tl + bl) : 1.0; + auto rightScale = tr + br > h ? static_cast(h) / (tr + br) : 1.0; + + tl = static_cast(tl * std::min(topScale, leftScale)); + tr = static_cast(tr * std::min(topScale, rightScale)); + bl = static_cast(bl * std::min(bottomScale, leftScale)); + br = static_cast(br * std::min(bottomScale, rightScale)); + + // Unlock each corner: subtract (cornerBox - quarterEllipse) from the + // full rect. Each corner only modifies pixels inside its own box, + // so no diagonal overlap is possible. + if (tl > 0) { + auto box = QRegion(x, y, tl, tl); + auto ellipse = QRegion(x, y, tl * 2, tl * 2, QRegion::Ellipse); + region -= box - (ellipse & box); + } + + if (tr > 0) { + auto box = QRegion(x + w - tr, y, tr, tr); + auto ellipse = QRegion(x + w - tr * 2, y, tr * 2, tr * 2, QRegion::Ellipse); + region -= box - (ellipse & box); + } + + if (bl > 0) { + auto box = QRegion(x, y + h - bl, bl, bl); + auto ellipse = QRegion(x, y + h - bl * 2, bl * 2, bl * 2, QRegion::Ellipse); + region -= box - (ellipse & box); + } + + if (br > 0) { + auto box = QRegion(x + w - br, y + h - br, br, br); + auto ellipse = QRegion(x + w - br * 2, y + h - br * 2, br * 2, br * 2, QRegion::Ellipse); + region -= box - (ellipse & box); + } + } + } + for (const auto& childRegion: this->mRegions) { region = childRegion->applyTo(region); } diff --git a/src/core/region.hpp b/src/core/region.hpp index 6637d7b..dfd1566 100644 --- a/src/core/region.hpp +++ b/src/core/region.hpp @@ -66,6 +66,29 @@ class PendingRegion: public QObject { Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged); /// Defaults to 0. Does nothing if @@item is set. Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged); + // clang-format off + /// Corner radius for rounded rectangles. Only applies when @@shape is `Rect`. Defaults to 0. + /// + /// Acts as the default for @@topLeftRadius, @@topRightRadius, @@bottomLeftRadius, + /// and @@bottomRightRadius. + Q_PROPERTY(qint32 radius READ radius WRITE setRadius NOTIFY radiusChanged); + /// Top-left corner radius. Only applies when @@shape is `Rect`. + /// + /// Defaults to @@radius, and may be reset by assigning `undefined`. + Q_PROPERTY(qint32 topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged); + /// Top-right corner radius. Only applies when @@shape is `Rect`. + /// + /// Defaults to @@radius, and may be reset by assigning `undefined`. + Q_PROPERTY(qint32 topRightRadius READ topRightRadius WRITE setTopRightRadius RESET resetTopRightRadius NOTIFY topRightRadiusChanged); + /// Bottom-left corner radius. Only applies when @@shape is `Rect`. + /// + /// Defaults to @@radius, and may be reset by assigning `undefined`. + Q_PROPERTY(qint32 bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius RESET resetBottomLeftRadius NOTIFY bottomLeftRadiusChanged); + /// Bottom-right corner radius. Only applies when @@shape is `Rect`. + /// + /// Defaults to @@radius, and may be reset by assigning `undefined`. + Q_PROPERTY(qint32 bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius RESET resetBottomRightRadius NOTIFY bottomRightRadiusChanged); + // clang-format on /// Regions to apply on top of this region. /// @@ -91,6 +114,25 @@ public: void setItem(QQuickItem* item); + [[nodiscard]] qint32 radius() const; + void setRadius(qint32 radius); + + [[nodiscard]] qint32 topLeftRadius() const; + void setTopLeftRadius(qint32 radius); + void resetTopLeftRadius(); + + [[nodiscard]] qint32 topRightRadius() const; + void setTopRightRadius(qint32 radius); + void resetTopRightRadius(); + + [[nodiscard]] qint32 bottomLeftRadius() const; + void setBottomLeftRadius(qint32 radius); + void resetBottomLeftRadius(); + + [[nodiscard]] qint32 bottomRightRadius() const; + void setBottomRightRadius(qint32 radius); + void resetBottomRightRadius(); + QQmlListProperty regions(); [[nodiscard]] bool empty() const; @@ -109,6 +151,11 @@ signals: void yChanged(); void widthChanged(); void heightChanged(); + void radiusChanged(); + void topLeftRadiusChanged(); + void topRightRadiusChanged(); + void bottomLeftRadiusChanged(); + void bottomRightRadiusChanged(); void childrenChanged(); /// Triggered when the region's geometry changes. @@ -130,12 +177,25 @@ private: static void regionsReplace(QQmlListProperty* prop, qsizetype i, PendingRegion* region); + enum CornerOverride : quint8 { + TopLeft = 0b1, + TopRight = 0b10, + BottomLeft = 0b100, + BottomRight = 0b1000, + }; + QQuickItem* mItem = nullptr; qint32 mX = 0; qint32 mY = 0; qint32 mWidth = 0; qint32 mHeight = 0; + qint32 mRadius = 0; + qint32 mTopLeftRadius = 0; + qint32 mTopRightRadius = 0; + qint32 mBottomLeftRadius = 0; + qint32 mBottomRightRadius = 0; + quint8 mCornerOverrides = 0; QList mRegions; }; diff --git a/src/core/scan.cpp b/src/core/scan.cpp index 58da38c..3605c52 100644 --- a/src/core/scan.cpp +++ b/src/core/scan.cpp @@ -145,10 +145,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna QString overrideText; bool isOverridden = false; - auto pragmaEngine = QJSEngine(); - pragmaEngine.globalObject().setPrototype( - pragmaEngine.newQObject(new qs::scan::env::PreprocEnv()) - ); + auto& pragmaEngine = *QmlScanner::preprocEngine(); auto postError = [&, this](QString error) { this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum}); @@ -370,3 +367,13 @@ QPair QmlScanner::jsonToQml(const QJsonValue& value, int inden return qMakePair(QStringLiteral("var"), "null"); } } + +QJSEngine* QmlScanner::preprocEngine() { + static auto* engine = [] { + auto* engine = new QJSEngine(); + engine->globalObject().setPrototype(engine->newQObject(new qs::scan::env::PreprocEnv())); + return engine; + }(); + + return engine; +} diff --git a/src/core/scan.hpp b/src/core/scan.hpp index 26034e1..7d807e1 100644 --- a/src/core/scan.hpp +++ b/src/core/scan.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -42,4 +43,6 @@ private: bool scanQmlFile(const QString& path, bool& singleton, bool& internal); bool scanQmlJson(const QString& path); [[nodiscard]] static QPair jsonToQml(const QJsonValue& value, int indent = 0); + + static QJSEngine* preprocEngine(); }; diff --git a/src/crash/handler.cpp b/src/crash/handler.cpp index 8f37085..33506a6 100644 --- a/src/crash/handler.cpp +++ b/src/crash/handler.cpp @@ -5,9 +5,11 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -58,6 +60,12 @@ void signalHandler( siginfo_t* /*info*/, // NOLINT (misc-include-cleaner) void* /*context*/ ) { + // NOLINTBEGIN (misc-include-cleaner) + sigset_t set; + sigfillset(&set); + sigprocmask(SIG_UNBLOCK, &set, nullptr); + // NOLINTEND + if (CrashInfo::INSTANCE.traceFd != -1) { auto traceBuffer = std::array(); auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1); @@ -79,13 +87,9 @@ void signalHandler( fail:; } + // TODO: coredump fork and crash reporter remain as zombies, fix auto coredumpPid = fork(); if (coredumpPid == 0) { - // NOLINTBEGIN (misc-include-cleaner) - sigset_t set; - sigfillset(&set); - sigprocmask(SIG_UNBLOCK, &set, nullptr); - // NOLINTEND raise(sig); _exit(-1); } @@ -131,7 +135,6 @@ void signalHandler( perror("Failed to fork and launch crash reporter.\n"); _exit(-1); } else if (pid == 0) { - // dup to remove CLOEXEC auto dumpFdStr = std::array(); auto logFdStr = std::array(); @@ -155,6 +158,21 @@ void signalHandler( } } +void handleCppTerminate() { + if (auto ptr = std::current_exception()) { + try { + std::rethrow_exception(ptr); + } catch (std::exception& e) { + qFatal().nospace() << "Terminate called with C++ exception (" + << cpptrace::demangle(typeid(e).name()).data() << "): " << e.what(); + } catch (...) { + qFatal() << "Terminate called with non exception object"; + } + } + + qFatal() << "Terminate called without active C++ exception"; +} + } // namespace void CrashHandler::init() { @@ -203,6 +221,8 @@ void CrashHandler::init() { // NOLINTEND (misc-include-cleaner) + std::set_terminate(&handleCppTerminate); + qCInfo(logCrashHandler) << "Crash handler initialized."; } diff --git a/src/crash/main.cpp b/src/crash/main.cpp index 05927f2..6533b43 100644 --- a/src/crash/main.cpp +++ b/src/crash/main.cpp @@ -25,6 +25,7 @@ #include "../core/logging.hpp" #include "../core/logging_p.hpp" #include "../core/paths.hpp" +#include "../core/plugin.hpp" #include "../core/ringbuf.hpp" #include "interface.hpp" @@ -230,12 +231,17 @@ void qsCheckCrash(int argc, char** argv) { ); auto app = QApplication(argc, argv); - QApplication::setDesktopFileName("org.quickshell"); + auto desktopId = + info.instance.appId.isEmpty() ? QStringLiteral("org.quickshell") : info.instance.appId; + QApplication::setDesktopFileName(desktopId); auto crashDir = QsPaths::crashDir(info.instance.instanceId); qCInfo(logCrashReporter) << "Starting crash reporter..."; + // Required platform compatibility hooks + QsEnginePlugin::preinitPluginsOnly(); + recordCrashInfo(crashDir, info.instance); auto gui = CrashReporterGui(crashDir.path(), crashProc); diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp index 3a9a2a5..0f5b090 100644 --- a/src/launch/launch.cpp +++ b/src/launch/launch.cpp @@ -76,6 +76,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio bool useSystemStyle = false; QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); QHash envOverrides; + QString appId = qEnvironmentVariable("QS_APP_ID"); QString dataDir; QString stateDir; QString cacheDir; @@ -104,6 +105,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio auto var = envPragma.sliced(0, splitIdx).trimmed(); auto val = envPragma.sliced(splitIdx + 1).trimmed(); pragmas.envOverrides.insert(var, val); + } else if (pragma.startsWith("AppId ")) { + pragmas.appId = pragma.sliced(6).trimmed(); } else if (pragma.startsWith("ShellId ")) { shellId = pragma.sliced(8).trimmed(); } else if (pragma.startsWith("DataDir ")) { @@ -128,10 +131,13 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio qInfo() << "Shell ID:" << shellId << "Path ID" << pathId; auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch(); + auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId; + InstanceInfo::CURRENT = InstanceInfo { .instanceId = base36Encode(getpid()) + base36Encode(launchTime), .configPath = args.configPath, .shellId = shellId, + .appId = appId, .launchTime = qs::Common::LAUNCH_TIME, .pid = getpid(), .display = getDisplayConnection(), @@ -231,7 +237,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio app = new QGuiApplication(qArgC, argv); } - QGuiApplication::setDesktopFileName("org.quickshell"); + QGuiApplication::setDesktopFileName(appId); if (args.debugPort != -1) { QQmlDebuggingEnabler::enableDebugging(true); diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 13e648a..cf84713 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -68,6 +68,7 @@ function (wl_proto target name dir) target_include_directories(${target} INTERFACE ${PROTO_BUILD_PATH}) target_link_libraries(${target} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate) qs_pch(${target} SET wayland-protocol) + target_compile_options(wl-proto-${name}-wl PRIVATE ${wayland_CFLAGS}) endfunction() # ----- @@ -83,10 +84,7 @@ qt_add_library(quickshell-wayland STATIC # required for wl_proxy_safe_deref target_link_libraries(quickshell-wayland PRIVATE ${CMAKE_DL_LIBS}) -target_link_options(quickshell PRIVATE - "LINKER:--export-dynamic-symbol=wl_proxy_get_listener" - "LINKER:--require-defined=wl_proxy_get_listener" -) +target_link_options(quickshell PRIVATE "LINKER:--export-dynamic-symbol=wl_proxy_get_listener") # required to make sure the constructor is linked add_library(quickshell-wayland-init OBJECT init.cpp) @@ -122,6 +120,9 @@ if (HYPRLAND) add_subdirectory(hyprland) endif() +add_subdirectory(background_effect) +list(APPEND WAYLAND_MODULES Quickshell.Wayland._BackgroundEffect) + add_subdirectory(idle_inhibit) list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) diff --git a/src/wayland/background_effect/CMakeLists.txt b/src/wayland/background_effect/CMakeLists.txt new file mode 100644 index 0000000..f45f94d --- /dev/null +++ b/src/wayland/background_effect/CMakeLists.txt @@ -0,0 +1,24 @@ +qt_add_library(quickshell-wayland-background-effect STATIC + manager.cpp + surface.cpp + qml.cpp +) + +qt_add_qml_module(quickshell-wayland-background-effect + URI Quickshell.Wayland._BackgroundEffect + VERSION 0.1 + DEPENDENCIES QtQml +) + +install_qml_module(quickshell-wayland-background-effect) + +wl_proto(wlp-background-effect ext-background-effect-v1 "${WAYLAND_PROTOCOLS}/staging/ext-background-effect") + +target_link_libraries(quickshell-wayland-background-effect PRIVATE + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client + wlp-background-effect +) + +qs_module_pch(quickshell-wayland-background-effect) + +target_link_libraries(quickshell PRIVATE quickshell-wayland-background-effectplugin) diff --git a/src/wayland/background_effect/manager.cpp b/src/wayland/background_effect/manager.cpp new file mode 100644 index 0000000..4cb06f1 --- /dev/null +++ b/src/wayland/background_effect/manager.cpp @@ -0,0 +1,38 @@ +#include "manager.hpp" +#include + +#include +#include +#include +#include + +#include "surface.hpp" + +namespace qs::wayland::background_effect::impl { + +BackgroundEffectManager::BackgroundEffectManager(): QWaylandClientExtensionTemplate(1) { + this->initialize(); +} + +BackgroundEffectSurface* +BackgroundEffectManager::createEffectSurface(QtWaylandClient::QWaylandWindow* window) { + return new BackgroundEffectSurface(this->get_background_effect(window->surface())); +} + +bool BackgroundEffectManager::blurAvailable() const { + return this->isActive() && this->mBlurAvailable; +} + +void BackgroundEffectManager::ext_background_effect_manager_v1_capabilities(uint32_t flags) { + auto available = static_cast(flags & capability_blur); + if (available == this->mBlurAvailable) return; + this->mBlurAvailable = available; + emit this->blurAvailableChanged(); +} + +BackgroundEffectManager* BackgroundEffectManager::instance() { + static auto* instance = new BackgroundEffectManager(); // NOLINT + return instance->isInitialized() ? instance : nullptr; +} + +} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/manager.hpp b/src/wayland/background_effect/manager.hpp new file mode 100644 index 0000000..6c2e981 --- /dev/null +++ b/src/wayland/background_effect/manager.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "surface.hpp" + +namespace qs::wayland::background_effect::impl { + +class BackgroundEffectManager + : public QWaylandClientExtensionTemplate + , public QtWayland::ext_background_effect_manager_v1 { + Q_OBJECT; + +public: + explicit BackgroundEffectManager(); + + BackgroundEffectSurface* createEffectSurface(QtWaylandClient::QWaylandWindow* window); + + [[nodiscard]] bool blurAvailable() const; + + static BackgroundEffectManager* instance(); + +signals: + void blurAvailableChanged(); + +protected: + void ext_background_effect_manager_v1_capabilities(uint32_t flags) override; + +private: + bool mBlurAvailable = false; +}; + +} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/qml.cpp b/src/wayland/background_effect/qml.cpp new file mode 100644 index 0000000..b54a847 --- /dev/null +++ b/src/wayland/background_effect/qml.cpp @@ -0,0 +1,246 @@ +#include "qml.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../core/region.hpp" +#include "../../window/proxywindow.hpp" +#include "../../window/windowinterface.hpp" +#include "manager.hpp" +#include "surface.hpp" + +using QtWaylandClient::QWaylandWindow; + +namespace qs::wayland::background_effect { + +BackgroundEffect* BackgroundEffect::qmlAttachedProperties(QObject* object) { + auto* proxyWindow = qobject_cast(object); + + if (!proxyWindow) { + if (auto* iface = qobject_cast(object)) { + proxyWindow = iface->proxyWindow(); + } + } + + if (!proxyWindow) return nullptr; + return new BackgroundEffect(proxyWindow); +} + +BackgroundEffect::BackgroundEffect(ProxyWindowBase* window): QObject(nullptr), proxyWindow(window) { + QObject::connect( + window, + &ProxyWindowBase::windowConnected, + this, + &BackgroundEffect::onWindowConnected + ); + + QObject::connect(window, &ProxyWindowBase::polished, this, &BackgroundEffect::onWindowPolished); + + QObject::connect( + window, + &ProxyWindowBase::devicePixelRatioChanged, + this, + &BackgroundEffect::updateBlurRegion + ); + + QObject::connect(window, &QObject::destroyed, this, &BackgroundEffect::onProxyWindowDestroyed); + + if (window->backingWindow()) { + this->onWindowConnected(); + } +} + +PendingRegion* BackgroundEffect::blurRegion() const { return this->mBlurRegion; } + +void BackgroundEffect::setBlurRegion(PendingRegion* region) { + if (region == this->mBlurRegion) return; + + if (this->mBlurRegion) { + QObject::disconnect(this->mBlurRegion, nullptr, this, nullptr); + } + + this->mBlurRegion = region; + + if (region) { + QObject::connect(region, &QObject::destroyed, this, &BackgroundEffect::onBlurRegionDestroyed); + QObject::connect(region, &PendingRegion::changed, this, &BackgroundEffect::updateBlurRegion); + } + + this->updateBlurRegion(); + emit this->blurRegionChanged(); +} + +void BackgroundEffect::onBlurRegionDestroyed() { + this->mBlurRegion = nullptr; + this->updateBlurRegion(); + emit this->blurRegionChanged(); +} + +void BackgroundEffect::updateBlurRegion() { + if (!this->surface || !this->proxyWindow) return; + + this->pendingBlurRegion = true; + this->proxyWindow->schedulePolish(); +} + +void BackgroundEffect::onWindowPolished() { + if (!this->surface || !this->pendingBlurRegion) return; + if (!this->mWaylandWindow || !this->mWaylandWindow->surface()) { + this->pendingBlurRegion = false; + return; + } + + QRegion region; + if (this->mBlurRegion) { + region = + this->mBlurRegion->applyTo(QRect(0, 0, this->mWindow->width(), this->mWindow->height())); + + auto scale = QHighDpiScaling::factor(this->mWindow); + if (!qFuzzyCompare(scale, 1.0)) { + region = QHighDpi::scale(region, scale); + } + + auto margins = this->mWaylandWindow->clientSideMargins(); + region.translate(margins.left(), margins.top()); + } + + this->surface->setBlurRegion(region); + this->pendingBlurRegion = false; +} + +bool BackgroundEffect::eventFilter(QObject* object, QEvent* event) { + if (event->type() == QEvent::PlatformSurface) { + auto* surfaceEvent = dynamic_cast(event); + if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { + this->surface = nullptr; + this->pendingBlurRegion = false; + } + } + + return this->QObject::eventFilter(object, event); +} + +void BackgroundEffect::onWindowConnected() { + this->mWindow = this->proxyWindow->backingWindow(); + this->mWindow->installEventFilter(this); + + QObject::connect( + this->mWindow, + &QWindow::visibleChanged, + this, + &BackgroundEffect::onWindowVisibleChanged + ); + + this->onWindowVisibleChanged(); +} + +void BackgroundEffect::onWindowVisibleChanged() { + if (this->mWindow->isVisible()) { + if (!this->mWindow->handle()) { + this->mWindow->create(); + } + } + + auto* window = dynamic_cast(this->mWindow->handle()); + if (window == this->mWaylandWindow) return; + + if (this->mWaylandWindow) { + QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); + } + + this->mWaylandWindow = window; + if (!window) return; + + QObject::connect( + this->mWaylandWindow, + &QObject::destroyed, + this, + &BackgroundEffect::onWaylandWindowDestroyed + ); + + QObject::connect( + this->mWaylandWindow, + &QWaylandWindow::surfaceCreated, + this, + &BackgroundEffect::onWaylandSurfaceCreated + ); + + QObject::connect( + this->mWaylandWindow, + &QWaylandWindow::surfaceDestroyed, + this, + &BackgroundEffect::onWaylandSurfaceDestroyed + ); + + if (this->mWaylandWindow->surface()) { + this->onWaylandSurfaceCreated(); + } +} + +void BackgroundEffect::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } + +void BackgroundEffect::onWaylandSurfaceCreated() { + auto* manager = impl::BackgroundEffectManager::instance(); + + if (!manager) { + qWarning() << "Cannot enable background effect as ext-background-effect-v1 is not supported " + "by the current compositor."; + return; + } + + // Steal protocol surface from previous BackgroundEffect to avoid duplicate-attachment on reload. + auto v = this->mWaylandWindow->property("qs_background_effect"); + if (v.canConvert()) { + auto* prev = v.value(); + if (prev != this && prev->surface) { + this->surface.swap(prev->surface); + } + } + + if (!this->surface) { + this->surface = std::unique_ptr( + manager->createEffectSurface(this->mWaylandWindow) + ); + } + + this->mWaylandWindow->setProperty("qs_background_effect", QVariant::fromValue(this)); + + this->pendingBlurRegion = this->mBlurRegion != nullptr; + if (this->pendingBlurRegion) { + this->proxyWindow->schedulePolish(); + } +} + +void BackgroundEffect::onWaylandSurfaceDestroyed() { + this->surface = nullptr; + this->pendingBlurRegion = false; + + if (!this->proxyWindow) { + this->deleteLater(); + } +} + +void BackgroundEffect::onProxyWindowDestroyed() { + // Don't delete the BackgroundEffect, and therefore the impl::BackgroundEffectSurface + // until the wl_surface is destroyed. Deleting it when the proxy window is deleted would + // cause a frame without blur between the destruction of the ext_background_effect_surface_v1 + // and wl_surface objects. + + this->proxyWindow = nullptr; + + if (this->surface == nullptr) { + this->deleteLater(); + } +} + +} // namespace qs::wayland::background_effect diff --git a/src/wayland/background_effect/qml.hpp b/src/wayland/background_effect/qml.hpp new file mode 100644 index 0000000..dd93aec --- /dev/null +++ b/src/wayland/background_effect/qml.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "../../core/region.hpp" +#include "../../window/proxywindow.hpp" +#include "surface.hpp" + +namespace qs::wayland::background_effect { + +///! Background blur effect for Wayland surfaces. +/// Applies background blur behind a @@Quickshell.QsWindow or subclass, +/// as an attached object, using the [ext-background-effect-v1] Wayland protocol. +/// +/// > [!NOTE] Using a background effect requires the compositor support the +/// > [ext-background-effect-v1] protocol. +/// +/// [ext-background-effect-v1]: https://wayland.app/protocols/ext-background-effect-v1 +/// +/// #### Example +/// ```qml +/// @@Quickshell.PanelWindow { +/// id: root +/// color: "#80000000" +/// +/// BackgroundEffect.blurRegion: Region { item: root.contentItem } +/// } +/// ``` +class BackgroundEffect: public QObject { + Q_OBJECT; + // clang-format off + /// Region to blur behind the surface. Set to null to remove blur. + Q_PROPERTY(PendingRegion* blurRegion READ blurRegion WRITE setBlurRegion NOTIFY blurRegionChanged); + // clang-format on + QML_ELEMENT; + QML_UNCREATABLE("BackgroundEffect can only be used as an attached object."); + QML_ATTACHED(BackgroundEffect); + +public: + explicit BackgroundEffect(ProxyWindowBase* window); + + [[nodiscard]] PendingRegion* blurRegion() const; + void setBlurRegion(PendingRegion* region); + + static BackgroundEffect* qmlAttachedProperties(QObject* object); + + bool eventFilter(QObject* object, QEvent* event) override; + +signals: + void blurRegionChanged(); + +private slots: + void onWindowConnected(); + void onWindowVisibleChanged(); + void onWaylandWindowDestroyed(); + void onWaylandSurfaceCreated(); + void onWaylandSurfaceDestroyed(); + void onProxyWindowDestroyed(); + void onBlurRegionDestroyed(); + void onWindowPolished(); + void updateBlurRegion(); + +private: + ProxyWindowBase* proxyWindow = nullptr; + QWindow* mWindow = nullptr; + QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; + + bool pendingBlurRegion = false; + PendingRegion* mBlurRegion = nullptr; + std::unique_ptr surface; +}; + +} // namespace qs::wayland::background_effect diff --git a/src/wayland/background_effect/surface.cpp b/src/wayland/background_effect/surface.cpp new file mode 100644 index 0000000..648361d --- /dev/null +++ b/src/wayland/background_effect/surface.cpp @@ -0,0 +1,37 @@ +#include "surface.hpp" + +#include +#include +#include +#include +#include + +namespace qs::wayland::background_effect::impl { + +BackgroundEffectSurface::BackgroundEffectSurface( + ::ext_background_effect_surface_v1* surface // NOLINT(misc-include-cleaner) +) + : QtWayland::ext_background_effect_surface_v1(surface) {} + +BackgroundEffectSurface::~BackgroundEffectSurface() { + if (!this->isInitialized()) return; + this->destroy(); +} + +void BackgroundEffectSurface::setBlurRegion(const QRegion& region) { + if (!this->isInitialized()) return; + + if (region.isEmpty()) { + this->set_blur_region(nullptr); + return; + } + + static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance(); + auto* display = waylandIntegration->display(); + + auto* wlRegion = display->createRegion(region); + this->set_blur_region(wlRegion); + wl_region_destroy(wlRegion); // NOLINT(misc-include-cleaner) +} + +} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/surface.hpp b/src/wayland/background_effect/surface.hpp new file mode 100644 index 0000000..65b0bc8 --- /dev/null +++ b/src/wayland/background_effect/surface.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +namespace qs::wayland::background_effect::impl { + +class BackgroundEffectSurface: public QtWayland::ext_background_effect_surface_v1 { +public: + explicit BackgroundEffectSurface(::ext_background_effect_surface_v1* surface); + ~BackgroundEffectSurface() override; + Q_DISABLE_COPY_MOVE(BackgroundEffectSurface); + + void setBlurRegion(const QRegion& region); +}; + +} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/test/manual/background_effect.qml b/src/wayland/background_effect/test/manual/background_effect.qml new file mode 100644 index 0000000..679cb01 --- /dev/null +++ b/src/wayland/background_effect/test/manual/background_effect.qml @@ -0,0 +1,62 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Wayland + +FloatingWindow { + id: root + color: "transparent" + contentItem.palette.windowText: "white" + + ColumnLayout { + anchors.centerIn: parent + + CheckBox { + id: enableBox + checked: true + text: "Enable Blur" + } + + Button { + text: "Hide->Show" + onClicked: { + root.visible = false + showTimer.start() + } + } + + Timer { + id: showTimer + interval: 200 + onTriggered: root.visible = true + } + + Slider { + id: radiusSlider + from: 0 + to: 1000 + value: 100 + } + + component EdgeSlider: Slider { + from: -1 + to: 1000 + value: -1 + } + + EdgeSlider { id: topLeftSlider } + EdgeSlider { id: topRightSlider } + EdgeSlider { id: bottomLeftSlider } + EdgeSlider { id: bottomRightSlider } + } + + BackgroundEffect.blurRegion: Region { + item: enableBox.checked ? root.contentItem : null + radius: radiusSlider.value == -1 ? undefined : radiusSlider.value + topLeftRadius: topLeftSlider.value == -1 ? undefined : topLeftSlider.value + topRightRadius: topRightSlider.value == -1 ? undefined : topRightSlider.value + bottomLeftRadius: bottomLeftSlider.value == -1 ? undefined : bottomLeftSlider.value + bottomRightRadius: bottomRightSlider.value == -1 ? undefined : bottomRightSlider.value + } +} diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index ed9dbeb..47462fb 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include diff --git a/src/wayland/hyprland/focus_grab/qml.cpp b/src/wayland/hyprland/focus_grab/qml.cpp index e26a75a..cf1ac24 100644 --- a/src/wayland/hyprland/focus_grab/qml.cpp +++ b/src/wayland/hyprland/focus_grab/qml.cpp @@ -9,7 +9,6 @@ #include #include "../../../window/proxywindow.hpp" -#include "../../../window/windowinterface.hpp" #include "grab.hpp" #include "manager.hpp" @@ -38,8 +37,51 @@ QObjectList HyprlandFocusGrab::windows() const { return this->windowObjects; } void HyprlandFocusGrab::setWindows(QObjectList windows) { if (windows == this->windowObjects) return; + if (this->grab) this->grab->startTransaction(); + + for (auto* obj: this->windowObjects) { + if (windows.contains(obj)) continue; + QObject::disconnect(obj, nullptr, this, nullptr); + + auto* proxy = ProxyWindowBase::forObject(obj); + if (!proxy) continue; + + QObject::disconnect(proxy, nullptr, this, nullptr); + + if (this->grab && proxy->backingWindow()) { + this->grab->removeWindow(proxy->backingWindow()); + } + } + + for (auto it = windows.begin(); it != windows.end();) { + auto* proxy = ProxyWindowBase::forObject(*it); + if (!proxy) { + it = windows.erase(it); + continue; + } + + if (this->windowObjects.contains(*it)) { + ++it; + continue; + } + + QObject::connect(*it, &QObject::destroyed, this, &HyprlandFocusGrab::onObjectDestroyed); + QObject::connect( + proxy, + &ProxyWindowBase::windowConnected, + this, + &HyprlandFocusGrab::onProxyConnected + ); + + if (this->grab && proxy->backingWindow()) { + this->grab->addWindow(proxy->backingWindow()); + } + + ++it; + } + + if (this->grab) this->grab->completeTransaction(); this->windowObjects = std::move(windows); - this->syncWindows(); emit this->windowsChanged(); } @@ -75,59 +117,18 @@ void HyprlandFocusGrab::tryActivate() { QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared); this->grab->startTransaction(); - for (auto* proxy: this->trackedProxies) { - if (proxy->backingWindow() != nullptr) { + for (auto* obj: this->windowObjects) { + auto* proxy = ProxyWindowBase::forObject(obj); + if (proxy && proxy->backingWindow()) { this->grab->addWindow(proxy->backingWindow()); } } this->grab->completeTransaction(); } -void HyprlandFocusGrab::syncWindows() { - auto newProxy = QList(); - for (auto* windowObject: this->windowObjects) { - auto* proxyWindow = qobject_cast(windowObject); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(windowObject)) { - proxyWindow = iface->proxyWindow(); - } - } - - if (proxyWindow != nullptr) { - newProxy.push_back(proxyWindow); - } - } - - if (this->grab) this->grab->startTransaction(); - - for (auto* oldWindow: this->trackedProxies) { - if (!newProxy.contains(oldWindow)) { - QObject::disconnect(oldWindow, nullptr, this, nullptr); - - if (this->grab != nullptr && oldWindow->backingWindow() != nullptr) { - this->grab->removeWindow(oldWindow->backingWindow()); - } - } - } - - for (auto* newProxy: newProxy) { - if (!this->trackedProxies.contains(newProxy)) { - QObject::connect( - newProxy, - &ProxyWindowBase::windowConnected, - this, - &HyprlandFocusGrab::onProxyConnected - ); - - if (this->grab != nullptr && newProxy->backingWindow() != nullptr) { - this->grab->addWindow(newProxy->backingWindow()); - } - } - } - - this->trackedProxies = newProxy; - if (this->grab) this->grab->completeTransaction(); +void HyprlandFocusGrab::onObjectDestroyed(QObject* object) { + this->windowObjects.removeOne(object); + emit this->windowsChanged(); } } // namespace qs::hyprland diff --git a/src/wayland/hyprland/focus_grab/qml.hpp b/src/wayland/hyprland/focus_grab/qml.hpp index 705b0d3..97a10de 100644 --- a/src/wayland/hyprland/focus_grab/qml.hpp +++ b/src/wayland/hyprland/focus_grab/qml.hpp @@ -96,15 +96,13 @@ private slots: void onGrabActivated(); void onGrabCleared(); void onProxyConnected(); + void onObjectDestroyed(QObject* object); private: void tryActivate(); - void syncWindows(); bool targetActive = false; QObjectList windowObjects; - QList trackedProxies; - QList trackedWindows; focus_grab::FocusGrab* grab = nullptr; }; diff --git a/src/wayland/hyprland/surface/qml.cpp b/src/wayland/hyprland/surface/qml.cpp index c4f7d67..4575842 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -14,7 +14,6 @@ #include "../../../core/region.hpp" #include "../../../window/proxywindow.hpp" -#include "../../../window/windowinterface.hpp" #include "manager.hpp" #include "surface.hpp" @@ -23,13 +22,7 @@ using QtWaylandClient::QWaylandWindow; namespace qs::hyprland::surface { HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) { - auto* proxyWindow = qobject_cast(object); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(object)) { - proxyWindow = iface->proxyWindow(); - } - } + auto* proxyWindow = ProxyWindowBase::forObject(object); if (!proxyWindow) return nullptr; return new HyprlandWindow(proxyWindow); diff --git a/src/wayland/idle_inhibit/inhibitor.cpp b/src/wayland/idle_inhibit/inhibitor.cpp index efeeae1..bfea7a0 100644 --- a/src/wayland/idle_inhibit/inhibitor.cpp +++ b/src/wayland/idle_inhibit/inhibitor.cpp @@ -6,7 +6,6 @@ #include #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "proto.hpp" namespace qs::wayland::idle_inhibit { @@ -25,27 +24,13 @@ QObject* IdleInhibitor::window() const { return this->bWindowObject; } void IdleInhibitor::setWindow(QObject* window) { if (window == this->bWindowObject) return; - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); this->bWindowObject = proxyWindow ? window : nullptr; } void IdleInhibitor::boundWindowChanged() { auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow == this->proxyWindow) return; if (this->mWaylandWindow) { diff --git a/src/wayland/init.cpp b/src/wayland/init.cpp index 790cebb..579e42a 100644 --- a/src/wayland/init.cpp +++ b/src/wayland/init.cpp @@ -33,8 +33,9 @@ class WaylandPlugin: public QsEnginePlugin { return isWayland; } + void preinit() override { installWlProxySafeDeref(); } + void init() override { - installWlProxySafeDeref(); installPlatformMenuHook(); installPopupPositioner(); } diff --git a/src/wayland/module.md b/src/wayland/module.md index 9ad15ba..964fa76 100644 --- a/src/wayland/module.md +++ b/src/wayland/module.md @@ -8,5 +8,6 @@ headers = [ "idle_inhibit/inhibitor.hpp", "idle_notify/monitor.hpp", "shortcuts_inhibit/inhibitor.hpp", + "background_effect/qml.hpp", ] ----- diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp index 2fca9bc..a91d5e2 100644 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ b/src/wayland/shortcuts_inhibit/inhibitor.cpp @@ -9,7 +9,6 @@ #include #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "proto.hpp" namespace qs::wayland::shortcuts_inhibit { @@ -48,14 +47,7 @@ ShortcutInhibitor::~ShortcutInhibitor() { void ShortcutInhibitor::onBoundWindowChanged() { auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow == this->proxyWindow) return; if (this->proxyWindow) { diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp index 6a1d96b..cb53381 100644 --- a/src/wayland/toplevel_management/qml.cpp +++ b/src/wayland/toplevel_management/qml.cpp @@ -9,7 +9,6 @@ #include "../../core/qmlscreen.hpp" #include "../../core/util.hpp" #include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" #include "../output_tracking.hpp" #include "handle.hpp" #include "manager.hpp" @@ -73,13 +72,7 @@ void Toplevel::fullscreenOn(QuickshellScreenInfo* screen) { } void Toplevel::setRectangle(QObject* window, QRect rect) { - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } + auto* proxyWindow = ProxyWindowBase::forObject(window); if (proxyWindow != this->rectWindow) { if (this->rectWindow != nullptr) { diff --git a/src/wayland/wl_proxy_safe_deref.cpp b/src/wayland/wl_proxy_safe_deref.cpp index 0ebc258..2664a99 100644 --- a/src/wayland/wl_proxy_safe_deref.cpp +++ b/src/wayland/wl_proxy_safe_deref.cpp @@ -1,4 +1,3 @@ - #include #include #include diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 62126bd..8a20dfa 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -57,6 +57,12 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent) ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } +ProxyWindowBase* ProxyWindowBase::forObject(QObject* obj) { + if (auto* proxy = qobject_cast(obj)) return proxy; + if (auto* iface = qobject_cast(obj)) return iface->proxyWindow(); + return nullptr; +} + void ProxyWindowBase::onReload(QObject* oldInstance) { if (this->mVisible) this->window = this->retrieveWindow(oldInstance); auto wasVisible = this->window != nullptr && this->window->isVisible(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index aec821e..9ff66c4 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -66,6 +66,8 @@ public: explicit ProxyWindowBase(QObject* parent = nullptr); ~ProxyWindowBase() override; + static ProxyWindowBase* forObject(QObject* obj); + ProxyWindowBase(ProxyWindowBase&) = delete; ProxyWindowBase(ProxyWindowBase&&) = delete; void operator=(ProxyWindowBase&) = delete;