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)
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: "<details> <summary>General information</summary>
```
<Paste the contents of the file here inside of the triple backticks>
```
</details>"
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 <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.
label: Read the text above. Do not submit the report.
options:
- label: Yes I want this report to be deleted.
required: true

View file

@ -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 <path-to-log>`.
You can preview the log if you'd like using `qs log <path-to-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.

View file

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

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

View file

@ -129,12 +129,13 @@ QString envInfo() {
auto stream = QTextStream(&info);
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_",
"QT_",
"QML_",
"QML2_",
"QSG_",
"XDG_CURRENT_DESKTOP=",
};
for (const auto& prefix: prefixes) {

View file

@ -3,14 +3,14 @@
#include <qdatastream.h>
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;
}

View file

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

View file

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

View file

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

View file

@ -18,7 +18,6 @@
#include <qwindow.h>
#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<ProxyWindowBase*>(parentWindow)) {
} else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
window = proxy->backingWindow();
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
window = interface->proxyWindow()->backingWindow();
} else {
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
return false;

View file

@ -9,6 +9,18 @@ static QVector<QsEnginePlugin*> 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();
}

View file

@ -18,12 +18,14 @@ public:
virtual QString name() { return QString(); }
virtual QList<QString> 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();

View file

@ -11,7 +11,6 @@
#include <qwindow.h>
#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<ProxyWindowBase*>(window)) {
if (auto* proxy = ProxyWindowBase::forObject(window)) {
this->bProxyWindow = proxy;
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
this->bProxyWindow = interface->proxyWindow();
} else {
qWarning() << "Tried to set popup anchor window to" << window
<< "which is not a quickshell window.";

View file

@ -1,4 +1,5 @@
#include "region.hpp"
#include <algorithm>
#include <cmath>
#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::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> PendingRegion::regions() {
return QQmlListProperty<PendingRegion>(
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<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) {
region = childRegion->applyTo(region);
}

View file

@ -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<PendingRegion> 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<PendingRegion>* 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<PendingRegion*> mRegions;
};

View file

@ -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<QString, QString> 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;
}

View file

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

View file

@ -5,9 +5,11 @@
#include <csignal>
#include <cstdio>
#include <cstring>
#include <exception>
#include <cpptrace/basic.hpp>
#include <cpptrace/forward.hpp>
#include <cpptrace/utils.hpp>
#include <qdatastream.h>
#include <qfile.h>
#include <qlogging.h>
@ -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<cpptrace::frame_ptr, 1024>();
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<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
void CrashHandler::init() {
@ -203,6 +221,8 @@ void CrashHandler::init() {
// NOLINTEND (misc-include-cleaner)
std::set_terminate(&handleCppTerminate);
qCInfo(logCrashHandler) << "Crash handler initialized.";
}

View file

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

View file

@ -76,6 +76,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
bool useSystemStyle = false;
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
QHash<QString, QString> 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);

View file

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

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/eglext.h>
#include <GL/gl.h>
#include <GLES3/gl32.h>
#include <fcntl.h>
#include <gbm.h>
#include <libdrm/drm_fourcc.h>

View file

@ -9,7 +9,6 @@
#include <qwindow.h>
#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<ProxyWindowBase*>();
for (auto* windowObject: this->windowObjects) {
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();
void HyprlandFocusGrab::onObjectDestroyed(QObject* object) {
this->windowObjects.removeOne(object);
emit this->windowsChanged();
}
} // namespace qs::hyprland

View file

@ -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<ProxyWindowBase*> trackedProxies;
QList<QWindow*> trackedWindows;
focus_grab::FocusGrab* grab = nullptr;
};

View file

@ -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<ProxyWindowBase*>(object);
if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(object)) {
proxyWindow = iface->proxyWindow();
}
}
auto* proxyWindow = ProxyWindowBase::forObject(object);
if (!proxyWindow) return nullptr;
return new HyprlandWindow(proxyWindow);

View file

@ -6,7 +6,6 @@
#include <qtmetamacros.h>
#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<ProxyWindowBase*>(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(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<ProxyWindowBase*>(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow == this->proxyWindow) return;
if (this->mWaylandWindow) {

View file

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

View file

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

View file

@ -9,7 +9,6 @@
#include <qwindow.h>
#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<ProxyWindowBase*>(window);
if (!proxyWindow) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow == this->proxyWindow) return;
if (this->proxyWindow) {

View file

@ -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<ProxyWindowBase*>(window);
if (proxyWindow == nullptr) {
if (auto* iface = qobject_cast<WindowInterface*>(window)) {
proxyWindow = iface->proxyWindow();
}
}
auto* proxyWindow = ProxyWindowBase::forObject(window);
if (proxyWindow != this->rectWindow) {
if (this->rectWindow != nullptr) {

View file

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

View file

@ -57,6 +57,12 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent)
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) {
if (this->mVisible) this->window = this->retrieveWindow(oldInstance);
auto wasVisible = this->window != nullptr && this->window->isVisible();

View file

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