Compare commits

...

15 commits

Author SHA1 Message Date
outfoxxed
b83c39c8af
services/pipewire: add -fno-strict-overflow to fix PCH with pipewire
Unclear how this should be handled long term.
2026-03-30 21:42:08 -07:00
Mia Herkt
ee1100eb98
wayland/buffer: drop unused GLESv3 include
That one is often in a separate Mesa package and contrary to GLESv2
doesn’t come with a pkg-config file. Mildly annoying…
2026-03-29 09:31:28 +02:00
outfoxxed
9bf752ac33
crash: add std::terminate handler 2026-03-28 23:07:37 -07:00
outfoxxed
313f4e47f6
core: track XDG_CURRENT_DESKTOP in debuginfo 2026-03-28 20:28:03 -07:00
outfoxxed
6ef86dd5aa
crash: run platform compat hooks in crash reporter init
For some reason, QtWayland crashes we work around trigger in this
path. This was previously masked when the crash reporter didn't unmask
signals, as long as the original process crashed with SIGSEGV.
2026-03-28 20:27:57 -07:00
outfoxxed
308f1e249b
crash: unmask signals before reexec
Signals were previously left masked before reexec, causing UB if a
child were to crash again, instead of triggering the reporter.

This might've been responsible for a number of unexplainable bugs.
2026-03-28 20:27:48 -07:00
outfoxxed
08058326f0
core: reuse global pragma parsing js engine during scanning
QJSEngine cleanup is not fast or clean and results in speed
degradation over time if too many are destroyed.
2026-03-25 00:16:36 -07:00
bbedward
6a244c3c56
core/region: add per-corner radius support 2026-03-19 23:42:32 -07:00
bbedward
d745184823
wayland/background-effect: add ext-background-effect-v1 support 2026-03-19 23:39:21 -07:00
bbedward
77c04a9447
launch: add ability to override AppId via pragma or QS_APP_ID 2026-03-19 22:33:32 -07:00
Dan Aloni
eb6eaf59c7
core/log: add a mutex to protect stdoutStream
QTextStream is not thread safe.
2026-03-19 03:36:12 -07:00
Mia Herkt
7511545ee2
build: add missing wayland-client CFLAGS
Fixes #276
2026-03-19 03:30:11 -07:00
outfoxxed
0cb62920a7
hyprland/focus_grab: handle destruction of tracked windows 2026-03-18 02:39:22 -07:00
outfoxxed
3520c85d77
wayland: remove --require-defined linker argument
Not supported by lld
2026-03-17 19:42:47 -07:00
outfoxxed
3cf65af49f
docs: ask users not to submit v1 crash reports 2026-03-17 10:10:01 -07:00
41 changed files with 915 additions and 209 deletions

View file

@ -1,82 +1,17 @@
name: Crash Report (v1) name: Crash Report (v1)
description: Quickshell has crashed description: Quickshell has crashed (old)
labels: ["bug", "crash"] labels: ["unactionable"]
body: body:
- type: textarea - type: markdown
id: crashinfo
attributes: attributes:
label: General crash information value: |
description: | Thank you for taking the time to click the report button.
Paste the contents of the `info.txt` file in your crash folder here. At this point most of the worst issues in 0.2.1 and before have been fixed and we are
value: "<details> <summary>General information</summary> preparing for a new release. Please do not submit this report.
- type: checkboxes
id: donotcheck
```
<Paste the contents of the file here inside of the triple backticks>
```
</details>"
validations:
required: true
- type: textarea
id: userinfo
attributes: attributes:
label: What caused the crash label: Read the text above. Do not submit the report.
description: | options:
Any information likely to help debug the crash. What were you doing when the crash occurred, - label: Yes I want this report to be deleted.
what changes did you make, can you get it to happen again? required: true
- 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 <path-to-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 <pid>` 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.

View file

@ -9,21 +9,21 @@ body:
description: | description: |
Any information likely to help debug the crash. What were you doing when the crash occurred, 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? what changes did you make, can you get it to happen again?
- type: textarea - type: upload
id: report id: report
attributes: attributes:
label: Report file label: Report file
description: Attach `report.txt` here. description: Attach `report.txt` here.
validations: validations:
required: true required: true
- type: textarea - type: upload
id: logs id: logs
attributes: attributes:
label: Log file label: Log file
description: | description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it. 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 <path-to-log>`. You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
validations: validations:
required: true required: true
- type: textarea - type: textarea
@ -31,7 +31,7 @@ body:
attributes: attributes:
label: Configuration label: Configuration
description: | 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. Compress it into a zip, tar, etc.
This will help us reproduce the crash ourselves. This will help us reproduce the crash ourselves.

View file

@ -87,8 +87,9 @@ include(cmake/util.cmake)
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension) 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_definitions(_REENTRANT)
add_compile_options(-fno-strict-overflow)
if (FRAME_POINTERS) if (FRAME_POINTERS)
add_compile_options(-fno-omit-frame-pointer) add_compile_options(-fno-omit-frame-pointer)

View file

@ -27,6 +27,8 @@ set shell id.
- Added a way to detect if an icon is from the system icon theme or not. - Added a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy. - Added vulkan support to screencopy.
- Added generic WindowManager interface implementing ext-workspace. - Added generic WindowManager interface implementing ext-workspace.
- Added ext-background-effect window blur support.
- Added per-corner radius support to Region.
## Other Changes ## Other Changes
@ -38,6 +40,7 @@ set shell id.
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching. - Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling. - 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 `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 ## Bug Fixes
@ -60,6 +63,7 @@ set shell id.
- Desktop action order is now preserved. - Desktop action order is now preserved.
- Fixed partial socket reads in greetd and hyprland on slow machines. - Fixed partial socket reads in greetd and hyprland on slow machines.
- Worked around Qt bug causing crashes when plugging and unplugging monitors. - 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 ## Packaging Changes

View file

@ -129,12 +129,13 @@ QString envInfo() {
auto stream = QTextStream(&info); auto stream = QTextStream(&info);
for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
auto prefixes = std::array<std::string_view, 5> { auto prefixes = std::array<std::string_view, 6> {
"QS_", "QS_",
"QT_", "QT_",
"QML_", "QML_",
"QML2_", "QML2_",
"QSG_", "QSG_",
"XDG_CURRENT_DESKTOP=",
}; };
for (const auto& prefix: prefixes) { for (const auto& prefix: prefixes) {

View file

@ -3,14 +3,14 @@
#include <qdatastream.h> #include <qdatastream.h>
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime
<< info.display; << info.pid << info.display;
return stream; return stream;
} }
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime
>> info.display; >> info.pid >> info.display;
return stream; return stream;
} }

View file

@ -9,6 +9,7 @@ struct InstanceInfo {
QString instanceId; QString instanceId;
QString configPath; QString configPath;
QString shellId; QString shellId;
QString appId;
QDateTime launchTime; QDateTime launchTime;
pid_t pid = -1; pid_t pid = -1;
QString display; QString display;

View file

@ -14,6 +14,7 @@
#include <qlist.h> #include <qlist.h>
#include <qlogging.h> #include <qlogging.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmutex.h>
#include <qnamespace.h> #include <qnamespace.h>
#include <qobject.h> #include <qobject.h>
#include <qobjectdefs.h> #include <qobjectdefs.h>
@ -220,6 +221,7 @@ void LogManager::messageHandler(
} }
if (display) { if (display) {
auto locker = QMutexLocker(&self->stdoutMutex);
LogMessage::formatMessage( LogMessage::formatMessage(
self->stdoutStream, self->stdoutStream,
message, message,

View file

@ -10,6 +10,7 @@
#include <qlatin1stringview.h> #include <qlatin1stringview.h>
#include <qlogging.h> #include <qlogging.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmutex.h>
#include <qobject.h> #include <qobject.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
@ -135,6 +136,7 @@ private:
QHash<QLatin1StringView, CategoryFilter> allFilters; QHash<QLatin1StringView, CategoryFilter> allFilters;
QTextStream stdoutStream; QTextStream stdoutStream;
QMutex stdoutMutex;
LoggingThreadProxy threadProxy; LoggingThreadProxy threadProxy;
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel); friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);

View file

@ -18,7 +18,6 @@
#include <qwindow.h> #include <qwindow.h>
#include "../window/proxywindow.hpp" #include "../window/proxywindow.hpp"
#include "../window/windowinterface.hpp"
#include "iconprovider.hpp" #include "iconprovider.hpp"
#include "model.hpp" #include "model.hpp"
#include "platformmenu_p.hpp" #include "platformmenu_p.hpp"
@ -91,10 +90,8 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati
} else if (parentWindow == nullptr) { } else if (parentWindow == nullptr) {
qCritical() << "Cannot display PlatformMenuEntry with null parent window."; qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
return false; return false;
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) { } else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
window = proxy->backingWindow(); window = proxy->backingWindow();
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
window = interface->proxyWindow()->backingWindow();
} else { } else {
qCritical() << "PlatformMenuEntry.display() must be called with a window."; qCritical() << "PlatformMenuEntry.display() must be called with a window.";
return false; return false;

View file

@ -9,6 +9,18 @@ static QVector<QsEnginePlugin*> plugins; // NOLINT
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); } 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() { void QsEnginePlugin::initPlugins() {
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
@ -16,6 +28,10 @@ void QsEnginePlugin::initPlugins() {
return b->dependencies().contains(a->name()); return b->dependencies().contains(a->name());
}); });
for (QsEnginePlugin* plugin: plugins) {
plugin->preinit();
}
for (QsEnginePlugin* plugin: plugins) { for (QsEnginePlugin* plugin: plugins) {
plugin->init(); plugin->init();
} }

View file

@ -18,12 +18,14 @@ public:
virtual QString name() { return QString(); } virtual QString name() { return QString(); }
virtual QList<QString> dependencies() { return {}; } virtual QList<QString> dependencies() { return {}; }
virtual bool applies() { return true; } virtual bool applies() { return true; }
virtual void preinit() {}
virtual void init() {} virtual void init() {}
virtual void registerTypes() {} virtual void registerTypes() {}
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
virtual void onReload() {} virtual void onReload() {}
static void registerPlugin(QsEnginePlugin& plugin); static void registerPlugin(QsEnginePlugin& plugin);
static void preinitPluginsOnly();
static void initPlugins(); static void initPlugins();
static void runConstructGeneration(EngineGeneration& generation); static void runConstructGeneration(EngineGeneration& generation);
static void runOnReload(); static void runOnReload();

View file

@ -11,7 +11,6 @@
#include <qwindow.h> #include <qwindow.h>
#include "../window/proxywindow.hpp" #include "../window/proxywindow.hpp"
#include "../window/windowinterface.hpp"
#include "types.hpp" #include "types.hpp"
bool PopupAnchorState::operator==(const PopupAnchorState& other) const { bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
@ -40,10 +39,8 @@ void PopupAnchor::setWindowInternal(QObject* window) {
} }
if (window) { if (window) {
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) { if (auto* proxy = ProxyWindowBase::forObject(window)) {
this->bProxyWindow = proxy; this->bProxyWindow = proxy;
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
this->bProxyWindow = interface->proxyWindow();
} else { } else {
qWarning() << "Tried to set popup anchor window to" << window qWarning() << "Tried to set popup anchor window to" << window
<< "which is not a quickshell window."; << "which is not a quickshell window.";

View file

@ -1,4 +1,5 @@
#include "region.hpp" #include "region.hpp"
#include <algorithm>
#include <cmath> #include <cmath>
#include <qobject.h> #include <qobject.h>
@ -18,6 +19,11 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::heightChanged, 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); 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()); } 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> PendingRegion::regions() { QQmlListProperty<PendingRegion> PendingRegion::regions() {
return QQmlListProperty<PendingRegion>( return QQmlListProperty<PendingRegion>(
this, this,
@ -90,6 +169,60 @@ QRegion PendingRegion::build() const {
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type); 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<double>(w) / (tl + tr) : 1.0;
auto bottomScale = bl + br > w ? static_cast<double>(w) / (bl + br) : 1.0;
auto leftScale = tl + bl > h ? static_cast<double>(h) / (tl + bl) : 1.0;
auto rightScale = tr + br > h ? static_cast<double>(h) / (tr + br) : 1.0;
tl = static_cast<qint32>(tl * std::min(topScale, leftScale));
tr = static_cast<qint32>(tr * std::min(topScale, rightScale));
bl = static_cast<qint32>(bl * std::min(bottomScale, leftScale));
br = static_cast<qint32>(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) { for (const auto& childRegion: this->mRegions) {
region = childRegion->applyTo(region); region = childRegion->applyTo(region);
} }

View file

@ -66,6 +66,29 @@ class PendingRegion: public QObject {
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged); Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
/// Defaults to 0. Does nothing if @@item is set. /// Defaults to 0. Does nothing if @@item is set.
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged); 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. /// Regions to apply on top of this region.
/// ///
@ -91,6 +114,25 @@ public:
void setItem(QQuickItem* item); 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<PendingRegion> regions(); QQmlListProperty<PendingRegion> regions();
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;
@ -109,6 +151,11 @@ signals:
void yChanged(); void yChanged();
void widthChanged(); void widthChanged();
void heightChanged(); void heightChanged();
void radiusChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomLeftRadiusChanged();
void bottomRightRadiusChanged();
void childrenChanged(); void childrenChanged();
/// Triggered when the region's geometry changes. /// Triggered when the region's geometry changes.
@ -130,12 +177,25 @@ private:
static void static void
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region); regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
enum CornerOverride : quint8 {
TopLeft = 0b1,
TopRight = 0b10,
BottomLeft = 0b100,
BottomRight = 0b1000,
};
QQuickItem* mItem = nullptr; QQuickItem* mItem = nullptr;
qint32 mX = 0; qint32 mX = 0;
qint32 mY = 0; qint32 mY = 0;
qint32 mWidth = 0; qint32 mWidth = 0;
qint32 mHeight = 0; qint32 mHeight = 0;
qint32 mRadius = 0;
qint32 mTopLeftRadius = 0;
qint32 mTopRightRadius = 0;
qint32 mBottomLeftRadius = 0;
qint32 mBottomRightRadius = 0;
quint8 mCornerOverrides = 0;
QList<PendingRegion*> mRegions; QList<PendingRegion*> mRegions;
}; };

View file

@ -145,10 +145,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
QString overrideText; QString overrideText;
bool isOverridden = false; bool isOverridden = false;
auto pragmaEngine = QJSEngine(); auto& pragmaEngine = *QmlScanner::preprocEngine();
pragmaEngine.globalObject().setPrototype(
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
);
auto postError = [&, this](QString error) { auto postError = [&, this](QString error) {
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum}); this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
@ -370,3 +367,13 @@ QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int inden
return qMakePair(QStringLiteral("var"), "null"); 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;
}

View file

@ -4,6 +4,7 @@
#include <qcontainerfwd.h> #include <qcontainerfwd.h>
#include <qdir.h> #include <qdir.h>
#include <qhash.h> #include <qhash.h>
#include <qjsengine.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qvector.h> #include <qvector.h>
@ -42,4 +43,6 @@ private:
bool scanQmlFile(const QString& path, bool& singleton, bool& internal); bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
bool scanQmlJson(const QString& path); bool scanQmlJson(const QString& path);
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0); [[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
static QJSEngine* preprocEngine();
}; };

View file

@ -5,9 +5,11 @@
#include <csignal> #include <csignal>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <exception>
#include <cpptrace/basic.hpp> #include <cpptrace/basic.hpp>
#include <cpptrace/forward.hpp> #include <cpptrace/forward.hpp>
#include <cpptrace/utils.hpp>
#include <qdatastream.h> #include <qdatastream.h>
#include <qfile.h> #include <qfile.h>
#include <qlogging.h> #include <qlogging.h>
@ -58,6 +60,12 @@ void signalHandler(
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner) siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
void* /*context*/ void* /*context*/
) { ) {
// NOLINTBEGIN (misc-include-cleaner)
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_UNBLOCK, &set, nullptr);
// NOLINTEND
if (CrashInfo::INSTANCE.traceFd != -1) { if (CrashInfo::INSTANCE.traceFd != -1) {
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>(); auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1); auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
@ -79,13 +87,9 @@ void signalHandler(
fail:; fail:;
} }
// TODO: coredump fork and crash reporter remain as zombies, fix
auto coredumpPid = fork(); auto coredumpPid = fork();
if (coredumpPid == 0) { if (coredumpPid == 0) {
// NOLINTBEGIN (misc-include-cleaner)
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_UNBLOCK, &set, nullptr);
// NOLINTEND
raise(sig); raise(sig);
_exit(-1); _exit(-1);
} }
@ -131,7 +135,6 @@ void signalHandler(
perror("Failed to fork and launch crash reporter.\n"); perror("Failed to fork and launch crash reporter.\n");
_exit(-1); _exit(-1);
} else if (pid == 0) { } else if (pid == 0) {
// dup to remove CLOEXEC // dup to remove CLOEXEC
auto dumpFdStr = std::array<char, 48>(); auto dumpFdStr = std::array<char, 48>();
auto logFdStr = std::array<char, 48>(); auto logFdStr = std::array<char, 48>();
@ -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 } // namespace
void CrashHandler::init() { void CrashHandler::init() {
@ -203,6 +221,8 @@ void CrashHandler::init() {
// NOLINTEND (misc-include-cleaner) // NOLINTEND (misc-include-cleaner)
std::set_terminate(&handleCppTerminate);
qCInfo(logCrashHandler) << "Crash handler initialized."; qCInfo(logCrashHandler) << "Crash handler initialized.";
} }

View file

@ -25,6 +25,7 @@
#include "../core/logging.hpp" #include "../core/logging.hpp"
#include "../core/logging_p.hpp" #include "../core/logging_p.hpp"
#include "../core/paths.hpp" #include "../core/paths.hpp"
#include "../core/plugin.hpp"
#include "../core/ringbuf.hpp" #include "../core/ringbuf.hpp"
#include "interface.hpp" #include "interface.hpp"
@ -230,12 +231,17 @@ void qsCheckCrash(int argc, char** argv) {
); );
auto app = QApplication(argc, 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); auto crashDir = QsPaths::crashDir(info.instance.instanceId);
qCInfo(logCrashReporter) << "Starting crash reporter..."; qCInfo(logCrashReporter) << "Starting crash reporter...";
// Required platform compatibility hooks
QsEnginePlugin::preinitPluginsOnly();
recordCrashInfo(crashDir, info.instance); recordCrashInfo(crashDir, info.instance);
auto gui = CrashReporterGui(crashDir.path(), crashProc); auto gui = CrashReporterGui(crashDir.path(), crashProc);

View file

@ -76,6 +76,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
bool useSystemStyle = false; bool useSystemStyle = false;
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
QHash<QString, QString> envOverrides; QHash<QString, QString> envOverrides;
QString appId = qEnvironmentVariable("QS_APP_ID");
QString dataDir; QString dataDir;
QString stateDir; QString stateDir;
QString cacheDir; QString cacheDir;
@ -104,6 +105,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
auto var = envPragma.sliced(0, splitIdx).trimmed(); auto var = envPragma.sliced(0, splitIdx).trimmed();
auto val = envPragma.sliced(splitIdx + 1).trimmed(); auto val = envPragma.sliced(splitIdx + 1).trimmed();
pragmas.envOverrides.insert(var, val); pragmas.envOverrides.insert(var, val);
} else if (pragma.startsWith("AppId ")) {
pragmas.appId = pragma.sliced(6).trimmed();
} else if (pragma.startsWith("ShellId ")) { } else if (pragma.startsWith("ShellId ")) {
shellId = pragma.sliced(8).trimmed(); shellId = pragma.sliced(8).trimmed();
} else if (pragma.startsWith("DataDir ")) { } 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; qInfo() << "Shell ID:" << shellId << "Path ID" << pathId;
auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch(); auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch();
auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId;
InstanceInfo::CURRENT = InstanceInfo { InstanceInfo::CURRENT = InstanceInfo {
.instanceId = base36Encode(getpid()) + base36Encode(launchTime), .instanceId = base36Encode(getpid()) + base36Encode(launchTime),
.configPath = args.configPath, .configPath = args.configPath,
.shellId = shellId, .shellId = shellId,
.appId = appId,
.launchTime = qs::Common::LAUNCH_TIME, .launchTime = qs::Common::LAUNCH_TIME,
.pid = getpid(), .pid = getpid(),
.display = getDisplayConnection(), .display = getDisplayConnection(),
@ -231,7 +237,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
app = new QGuiApplication(qArgC, argv); app = new QGuiApplication(qArgC, argv);
} }
QGuiApplication::setDesktopFileName("org.quickshell"); QGuiApplication::setDesktopFileName(appId);
if (args.debugPort != -1) { if (args.debugPort != -1) {
QQmlDebuggingEnabler::enableDebugging(true); QQmlDebuggingEnabler::enableDebugging(true);

View file

@ -68,6 +68,7 @@ function (wl_proto target name dir)
target_include_directories(${target} INTERFACE ${PROTO_BUILD_PATH}) target_include_directories(${target} INTERFACE ${PROTO_BUILD_PATH})
target_link_libraries(${target} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate) target_link_libraries(${target} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate)
qs_pch(${target} SET wayland-protocol) qs_pch(${target} SET wayland-protocol)
target_compile_options(wl-proto-${name}-wl PRIVATE ${wayland_CFLAGS})
endfunction() endfunction()
# ----- # -----
@ -83,10 +84,7 @@ qt_add_library(quickshell-wayland STATIC
# required for wl_proxy_safe_deref # required for wl_proxy_safe_deref
target_link_libraries(quickshell-wayland PRIVATE ${CMAKE_DL_LIBS}) target_link_libraries(quickshell-wayland PRIVATE ${CMAKE_DL_LIBS})
target_link_options(quickshell PRIVATE target_link_options(quickshell PRIVATE "LINKER:--export-dynamic-symbol=wl_proxy_get_listener")
"LINKER:--export-dynamic-symbol=wl_proxy_get_listener"
"LINKER:--require-defined=wl_proxy_get_listener"
)
# required to make sure the constructor is linked # required to make sure the constructor is linked
add_library(quickshell-wayland-init OBJECT init.cpp) add_library(quickshell-wayland-init OBJECT init.cpp)
@ -122,6 +120,9 @@ if (HYPRLAND)
add_subdirectory(hyprland) add_subdirectory(hyprland)
endif() endif()
add_subdirectory(background_effect)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._BackgroundEffect)
add_subdirectory(idle_inhibit) add_subdirectory(idle_inhibit)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor)

View file

@ -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)

View file

@ -0,0 +1,38 @@
#include "manager.hpp"
#include <cstdint>
#include <private/qwaylandwindow_p.h>
#include <qtmetamacros.h>
#include <qwayland-ext-background-effect-v1.h>
#include <qwaylandclientextension.h>
#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<bool>(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

View file

@ -0,0 +1,37 @@
#pragma once
#include <private/qwaylandwindow_p.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qwayland-ext-background-effect-v1.h>
#include <qwaylandclientextension.h>
#include "surface.hpp"
namespace qs::wayland::background_effect::impl {
class BackgroundEffectManager
: public QWaylandClientExtensionTemplate<BackgroundEffectManager>
, 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

View file

@ -0,0 +1,246 @@
#include "qml.hpp"
#include <memory>
#include <private/qhighdpiscaling_p.h>
#include <private/qwaylandwindow_p.h>
#include <qcoreevent.h>
#include <qevent.h>
#include <qlogging.h>
#include <qnumeric.h>
#include <qobject.h>
#include <qregion.h>
#include <qtmetamacros.h>
#include <qvariant.h>
#include <qwindow.h>
#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<ProxyWindowBase*>(object);
if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(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<QPlatformSurfaceEvent*>(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<QWaylandWindow*>(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<BackgroundEffect*>()) {
auto* prev = v.value<BackgroundEffect*>();
if (prev != this && prev->surface) {
this->surface.swap(prev->surface);
}
}
if (!this->surface) {
this->surface = std::unique_ptr<impl::BackgroundEffectSurface>(
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

View file

@ -0,0 +1,80 @@
#pragma once
#include <memory>
#include <private/qwaylandwindow_p.h>
#include <qcoreevent.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qwindow.h>
#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<impl::BackgroundEffectSurface> surface;
};
} // namespace qs::wayland::background_effect

View file

@ -0,0 +1,37 @@
#include "surface.hpp"
#include <private/qwaylanddisplay_p.h>
#include <private/qwaylandintegration_p.h>
#include <qregion.h>
#include <qwayland-ext-background-effect-v1.h>
#include <wayland-client-protocol.h>
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

View file

@ -0,0 +1,18 @@
#pragma once
#include <qregion.h>
#include <qtclasshelpermacros.h>
#include <qwayland-ext-background-effect-v1.h>
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

View file

@ -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
}
}

View file

@ -10,7 +10,6 @@
#include <EGL/egl.h> #include <EGL/egl.h>
#include <EGL/eglext.h> #include <EGL/eglext.h>
#include <GL/gl.h> #include <GL/gl.h>
#include <GLES3/gl32.h>
#include <fcntl.h> #include <fcntl.h>
#include <gbm.h> #include <gbm.h>
#include <libdrm/drm_fourcc.h> #include <libdrm/drm_fourcc.h>

View file

@ -9,7 +9,6 @@
#include <qwindow.h> #include <qwindow.h>
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "../../../window/windowinterface.hpp"
#include "grab.hpp" #include "grab.hpp"
#include "manager.hpp" #include "manager.hpp"
@ -38,8 +37,51 @@ QObjectList HyprlandFocusGrab::windows() const { return this->windowObjects; }
void HyprlandFocusGrab::setWindows(QObjectList windows) { void HyprlandFocusGrab::setWindows(QObjectList windows) {
if (windows == this->windowObjects) return; 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->windowObjects = std::move(windows);
this->syncWindows();
emit this->windowsChanged(); emit this->windowsChanged();
} }
@ -75,59 +117,18 @@ void HyprlandFocusGrab::tryActivate() {
QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared); QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared);
this->grab->startTransaction(); this->grab->startTransaction();
for (auto* proxy: this->trackedProxies) { for (auto* obj: this->windowObjects) {
if (proxy->backingWindow() != nullptr) { auto* proxy = ProxyWindowBase::forObject(obj);
if (proxy && proxy->backingWindow()) {
this->grab->addWindow(proxy->backingWindow()); this->grab->addWindow(proxy->backingWindow());
} }
} }
this->grab->completeTransaction(); this->grab->completeTransaction();
} }
void HyprlandFocusGrab::syncWindows() { void HyprlandFocusGrab::onObjectDestroyed(QObject* object) {
auto newProxy = QList<ProxyWindowBase*>(); this->windowObjects.removeOne(object);
for (auto* windowObject: this->windowObjects) { emit this->windowsChanged();
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(windowObject);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(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();
} }
} // namespace qs::hyprland } // namespace qs::hyprland

View file

@ -96,15 +96,13 @@ private slots:
void onGrabActivated(); void onGrabActivated();
void onGrabCleared(); void onGrabCleared();
void onProxyConnected(); void onProxyConnected();
void onObjectDestroyed(QObject* object);
private: private:
void tryActivate(); void tryActivate();
void syncWindows();
bool targetActive = false; bool targetActive = false;
QObjectList windowObjects; QObjectList windowObjects;
QList<ProxyWindowBase*> trackedProxies;
QList<QWindow*> trackedWindows;
focus_grab::FocusGrab* grab = nullptr; focus_grab::FocusGrab* grab = nullptr;
}; };

View file

@ -14,7 +14,6 @@
#include "../../../core/region.hpp" #include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "../../../window/windowinterface.hpp"
#include "manager.hpp" #include "manager.hpp"
#include "surface.hpp" #include "surface.hpp"
@ -23,13 +22,7 @@ using QtWaylandClient::QWaylandWindow;
namespace qs::hyprland::surface { namespace qs::hyprland::surface {
HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) { HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) {
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(object); auto* proxyWindow = ProxyWindowBase::forObject(object);
if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(object)) {
proxyWindow = iface->proxyWindow();
}
}
if (!proxyWindow) return nullptr; if (!proxyWindow) return nullptr;
return new HyprlandWindow(proxyWindow); return new HyprlandWindow(proxyWindow);

View file

@ -6,7 +6,6 @@
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include "../../window/proxywindow.hpp" #include "../../window/proxywindow.hpp"
#include "../../window/windowinterface.hpp"
#include "proto.hpp" #include "proto.hpp"
namespace qs::wayland::idle_inhibit { namespace qs::wayland::idle_inhibit {
@ -25,27 +24,13 @@ QObject* IdleInhibitor::window() const { return this->bWindowObject; }
void IdleInhibitor::setWindow(QObject* window) { void IdleInhibitor::setWindow(QObject* window) {
if (window == this->bWindowObject) return; if (window == this->bWindowObject) return;
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window); auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
this->bWindowObject = proxyWindow ? window : nullptr; this->bWindowObject = proxyWindow ? window : nullptr;
} }
void IdleInhibitor::boundWindowChanged() { void IdleInhibitor::boundWindowChanged() {
auto* window = this->bBoundWindow.value(); auto* window = this->bBoundWindow.value();
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window); auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
if (proxyWindow == this->proxyWindow) return; if (proxyWindow == this->proxyWindow) return;
if (this->mWaylandWindow) { if (this->mWaylandWindow) {

View file

@ -33,8 +33,9 @@ class WaylandPlugin: public QsEnginePlugin {
return isWayland; return isWayland;
} }
void preinit() override { installWlProxySafeDeref(); }
void init() override { void init() override {
installWlProxySafeDeref();
installPlatformMenuHook(); installPlatformMenuHook();
installPopupPositioner(); installPopupPositioner();
} }

View file

@ -8,5 +8,6 @@ headers = [
"idle_inhibit/inhibitor.hpp", "idle_inhibit/inhibitor.hpp",
"idle_notify/monitor.hpp", "idle_notify/monitor.hpp",
"shortcuts_inhibit/inhibitor.hpp", "shortcuts_inhibit/inhibitor.hpp",
"background_effect/qml.hpp",
] ]
----- -----

View file

@ -9,7 +9,6 @@
#include <qwindow.h> #include <qwindow.h>
#include "../../window/proxywindow.hpp" #include "../../window/proxywindow.hpp"
#include "../../window/windowinterface.hpp"
#include "proto.hpp" #include "proto.hpp"
namespace qs::wayland::shortcuts_inhibit { namespace qs::wayland::shortcuts_inhibit {
@ -48,14 +47,7 @@ ShortcutInhibitor::~ShortcutInhibitor() {
void ShortcutInhibitor::onBoundWindowChanged() { void ShortcutInhibitor::onBoundWindowChanged() {
auto* window = this->bBoundWindow.value(); auto* window = this->bBoundWindow.value();
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window); auto* proxyWindow = ProxyWindowBase::forObject(window);
if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
if (proxyWindow == this->proxyWindow) return; if (proxyWindow == this->proxyWindow) return;
if (this->proxyWindow) { if (this->proxyWindow) {

View file

@ -9,7 +9,6 @@
#include "../../core/qmlscreen.hpp" #include "../../core/qmlscreen.hpp"
#include "../../core/util.hpp" #include "../../core/util.hpp"
#include "../../window/proxywindow.hpp" #include "../../window/proxywindow.hpp"
#include "../../window/windowinterface.hpp"
#include "../output_tracking.hpp" #include "../output_tracking.hpp"
#include "handle.hpp" #include "handle.hpp"
#include "manager.hpp" #include "manager.hpp"
@ -73,13 +72,7 @@ void Toplevel::fullscreenOn(QuickshellScreenInfo* screen) {
} }
void Toplevel::setRectangle(QObject* window, QRect rect) { void Toplevel::setRectangle(QObject* window, QRect rect) {
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(window); auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
if (proxyWindow != this->rectWindow) { if (proxyWindow != this->rectWindow) {
if (this->rectWindow != nullptr) { if (this->rectWindow != nullptr) {

View file

@ -1,4 +1,3 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <qlogging.h> #include <qlogging.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>

View file

@ -57,6 +57,12 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent)
ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); }
ProxyWindowBase* ProxyWindowBase::forObject(QObject* obj) {
if (auto* proxy = qobject_cast<ProxyWindowBase*>(obj)) return proxy;
if (auto* iface = qobject_cast<WindowInterface*>(obj)) return iface->proxyWindow();
return nullptr;
}
void ProxyWindowBase::onReload(QObject* oldInstance) { void ProxyWindowBase::onReload(QObject* oldInstance) {
if (this->mVisible) this->window = this->retrieveWindow(oldInstance); if (this->mVisible) this->window = this->retrieveWindow(oldInstance);
auto wasVisible = this->window != nullptr && this->window->isVisible(); auto wasVisible = this->window != nullptr && this->window->isVisible();

View file

@ -66,6 +66,8 @@ public:
explicit ProxyWindowBase(QObject* parent = nullptr); explicit ProxyWindowBase(QObject* parent = nullptr);
~ProxyWindowBase() override; ~ProxyWindowBase() override;
static ProxyWindowBase* forObject(QObject* obj);
ProxyWindowBase(ProxyWindowBase&) = delete; ProxyWindowBase(ProxyWindowBase&) = delete;
ProxyWindowBase(ProxyWindowBase&&) = delete; ProxyWindowBase(ProxyWindowBase&&) = delete;
void operator=(ProxyWindowBase&) = delete; void operator=(ProxyWindowBase&) = delete;