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;