31 Commits
Author SHA1 Message Date
outfoxxed 1a4716cde7 version: 0.3.1 2026-08-20 19:28:55 -07:00
outfoxxed 069e33a93e core/scriptmodel: structurally compare model entries
Comparisons regressed to being identity based when the values list was
changed from being a `QList<QVariant>` to `QList<QJSValue>`.
2026-08-20 19:08:57 -07:00
outfoxxed 532a0606ca core/scriptmodel: handle reentrancy in ScriptModel::setValues 2026-08-16 00:22:04 -07:00
outfoxxed 78672a8405 x11/panelwindow: avoid using destroyed panel stack after free 2026-08-15 20:53:19 -07:00
outfoxxed 990a8badab core/popupanchor: null check mItem in onItemWindowChanged
Also emit itemChanged in PopupAnchor::setItem().
2026-08-15 20:53:19 -07:00
outfoxxed 7e6c7ae3fb services/pipewire: match node and stream channels in peak sampling
Also fixes potentially requeuing a null buffer
2026-08-15 20:53:19 -07:00
outfoxxed 8f1316d0c2 wayland/screencopy: fix bo double free on alloc fail in createDmabuf
gbm_bo_free is already called by the WlDmaBuffer destructor
2026-08-15 20:53:19 -07:00
outfoxxed 63eb2ba6ef core/colorquant: copy source to operations and cancel on destroy
Fixes UAFing the source url in ColorQuantizerOperation if the
ColorQuantizer is destroyed.
2026-08-15 20:53:18 -07:00
outfoxxed b1fc3e0740 io/fileview: disconnect deleteLater'd file watchers immediately
Without this disconnect, the watcher can send a signal whose handler
references the (now null) watcher pointer before the watcher is deleted.
2026-08-15 16:04:18 -07:00
outfoxxed 21fec77de7 wayland/toplevel: null check window passed to Toplevel::setRectangle
Fixes Toplevel::unsetRectangle, which passes a null.
2026-08-15 15:55:46 -07:00
outfoxxed d9cbc90a8c io/fileview: fix swapped new/old branch in JsonAdapter deserialize 2026-08-15 14:44:52 -07:00
outfoxxed 28771c7c74 ipc: ensure handler deregistration upon destruction
Deregistration when an engine generation lookup fails has been
reported as an issue but couldn't be reproduced. This is a speculative
fix.
2026-08-02 13:53:41 -07:00
outfoxxed 43d4fa9e88 crash: strip crash env and avoid leaking crash info fd on launch 2026-07-26 23:37:18 -07:00
outfoxxed 10b439fc6e launch: prevent double initialization of logger 2026-07-23 02:42:56 -07:00
outfoxxed e649d24749 launch: don't print dead instance help logs from --no-duplicate 2026-07-20 19:22:17 -07:00
outfoxxed 025c709e94 ipc: wait for remote process to exit before returning from kill 2026-07-20 19:21:27 -07:00
outfoxxed 4df562dfb2 service/notifications: emit NotificationClosed after Notify returns 2026-07-10 01:58:38 -07:00
outfoxxed 98b6d0b7e7 io/ipchandler: stop reimplementing QVariant in ipc value slots 2026-07-10 01:26:48 -07:00
outfoxxed 25eb04fb06 tooling: use QList<QJSValue> over QJSValueList
QJSValueList is not currently a defined QML type, breaking qmllint and qmlls.
2026-07-08 01:23:01 -07:00
outfoxxed ead3b00afd networking: fix UAFs in NMWirelessNetwork ap/setting destroy handler 2026-07-07 00:34:06 -07:00
outfoxxed a611932352 core/scriptmodel: represent members as a QJSValueList
Fixes UAFs caused by access to pointers stored either directly or
contained in an object inside a ScriptModel which were freed and then
accessed.

These accesses could be observed by not setting `values` to a binding,
access before updateValuesUnique finished, or randomly before a
binding had been fully updated.
2026-07-06 23:45:42 -07:00
outfoxxed 1661ef100a wm: recompute WindowsetProjection.screens on each usage
Fixes potential UAF from destroyed screens not yet removed by a
wayland commit.
2026-06-30 02:31:19 -07:00
bbedward 68c2c85c33 core/window: render a frame when re-enabling updatesEnabled 2026-06-18 22:43:11 -07:00
luke 897fcdaa99 wayland/lock: avoid creating lock surfaces for uninited wl screens 2026-06-17 22:59:12 -07:00
outfoxxed d99d87d5e5 crash: remove logs from crash template 2026-06-09 18:04:48 -07:00
outfoxxed b66495fcc5 all: add/fix property change signals on various properties
- FileView.atomicWrites notified on blockWrites
- StatusNotifierItem.onlyMenu missing notification
- MarginWrapper.extraMargin missing notification
- MarginWrapper.resizeChild missing notification
- PopupWindow.grabFocus missing notification
2026-06-01 01:48:57 -07:00
outfoxxed eaba6c8b62 services/mpris: fix minRate and maxRate returning rate 2026-06-01 01:37:19 -07:00
outfoxxed 4b4fca3224 docs: document private ABI usage 2026-05-21 23:14:12 -07:00
outfoxxed 7d1c9a9c67 wayland/toplevel: reorganize toplevel management 2026-05-11 01:38:16 -07:00
Aditya Alok d1760ed1f3 wayland/lock: add window null check in surface isVisible 2026-05-07 23:40:27 -07:00
outfoxxed b93d61385f wayland/lock: run scenegraph callbacks for lock surfaces
Fixes ScreencopyView never initializing if only using lock surfaces.
2026-05-07 03:08:57 -07:00
61 changed files with 901 additions and 745 deletions

No files matched your search

+3 -9
View File
@@ -13,17 +13,11 @@ body:
id: report
attributes:
label: Report file
description: Attach `report.txt` here.
validations:
required: true
- type: upload
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it.
Attach `report.txt` here.
You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
Please ensure the report does not contain personal information,
as reports may contain detailed logs leading up to the crash.
validations:
required: true
- type: textarea
+3 -7
View File
@@ -39,6 +39,9 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `pkg-config` (build-time)
- `cli11` (static library)
Quickshell relies on private Qt APIs and **MUST** be rebuilt against the each Qt release
or crashes via ABI mismatches will occur.
Build time dependencies and static libraries don't have to exist at runtime,
however build time dependencies must be compiled for the architecture of
the builder, while static libraries must be compiled for the architecture
@@ -97,13 +100,6 @@ To disable: `-DSOCKETS=OFF`
### Wayland
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
at runtime.
Currently supported Qt versions: `6.6`, `6.7`.
To disable: `-DWAYLAND=OFF`
Dependencies:
+1 -1
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.3.0" LANGUAGES CXX C)
project(quickshell VERSION "0.3.1" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)
+23
View File
@@ -0,0 +1,23 @@
## Bug Fixes
- Fixed `ScreencopyView` not displaying when only lock surfaces are shown.
- Fixed `WlSessionLockSurface.visible` crashing if accessed before backing surface creation.
- Fixed `MprisPlayer` returning `rate` for `minRate` and `maxRate`.
- Fixed missing/wrong change signals on various properties.
- Fixed session lock crashes on sleep, wake, DPMS, and unlocking.
- `QsWindow.updatesEnabled` makes sure windows are redrawn when set to true.
- Fixed potential crashes from usage of `WindowsetProjection.screens` during monitor unplug.
- Fixed crashes from accessing freed objects laundered through a `ScriptModel`.
- Fixed crashes when a wifi network disappear.
- Fixed unhandled notifications sending `NotificationClosed` out of order.
- Fixed `qs kill` not waiting for the process to exit.
- Fixed IPC calls from children of a crashed and relaunched process crashing.
- Fixed `JsonAdapter` crashing when deserializing new objects into an array.
- Fixed `Toplevel.unsetRectangle` crashing when calle.
- Fixed intermittent `FileView` crashes when updating watched files.
- Fixed `ColorQuantizer` deletion crashing if an operation was live.
- Fixed crashes when failing to create a `ScreencopyView`.
- Fixed `PwNodePeakMonitor` crashing when sampling a stream with mismatched channels from the node.
- Fixed unsetting `PopupAnchor.item` causing a crash.
- Fixed hiding the last `PanelWindow` on screen causing a crash under X11.
- Fixed crashes when `ScriptModel.values` is set while processing previously set values.
+1 -1
View File
@@ -56,7 +56,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.3.0";
version = "0.3.1";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
+9 -6
View File
@@ -1,5 +1,6 @@
#include "colorquantizer.hpp"
#include <algorithm>
#include <utility>
#include <qatomic.h>
#include <qcolor.h>
@@ -26,12 +27,12 @@ QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg
}
ColorQuantizerOperation::ColorQuantizerOperation(
QUrl* source,
QUrl source,
qreal depth,
QRect imageRect,
qreal rescaleSize
)
: source(source)
: source(std::move(source))
, maxDepth(depth)
, imageRect(imageRect)
, rescaleSize(rescaleSize) {
@@ -39,11 +40,11 @@ ColorQuantizerOperation::ColorQuantizerOperation(
}
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
if (shouldCancel.loadAcquire() || this->source.isEmpty()) return;
this->colors.clear();
auto image = QImage(this->source->toLocalFile());
auto image = QImage(this->source.toLocalFile());
if (this->imageRect.isValid()) {
image = image.copy(this->imageRect);
@@ -61,7 +62,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
}
if (image.isNull()) {
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source.toString();
return;
}
@@ -191,6 +192,8 @@ void ColorQuantizerOperation::run() {
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
ColorQuantizer::~ColorQuantizer() { this->cancelAsync(); }
void ColorQuantizer::componentComplete() {
this->componentCompleted = true;
if (!this->mSource.isEmpty()) this->quantizeAsync();
@@ -246,7 +249,7 @@ void ColorQuantizer::quantizeAsync() {
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
this->liveOperation = new ColorQuantizerOperation(
&this->mSource,
this->mSource,
this->mDepth,
this->mImageRect,
this->mRescaleSize
+5 -2
View File
@@ -7,6 +7,7 @@
#include <qqmlparserstatus.h>
#include <qrect.h>
#include <qrunnable.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
@@ -17,7 +18,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
void run() override;
void tryCancel();
@@ -43,7 +44,7 @@ private:
QAtomicInteger<bool> shouldCancel = false;
QList<QColor> colors;
QUrl* source;
QUrl source;
qreal maxDepth;
QRect imageRect;
qreal rescaleSize;
@@ -94,6 +95,8 @@ class ColorQuantizer
public:
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
~ColorQuantizer() override;
Q_DISABLE_COPY_MOVE(ColorQuantizer);
void componentComplete() override;
void classBegin() override {}
+4
View File
@@ -304,6 +304,10 @@ void LogManager::init(
const QString& rules,
const QString& prefix
) {
static bool alreadyInitialized = false;
if (alreadyInitialized) return;
alreadyInitialized = true;
auto* instance = LogManager::instance();
instance->colorLogs = color;
instance->timestampLogs = timestamp;
+3 -1
View File
@@ -93,6 +93,8 @@ void PopupAnchor::setItem(QQuickItem* item) {
QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
}
emit this->itemChanged();
}
void PopupAnchor::onWindowDestroyed() {
@@ -109,7 +111,7 @@ void PopupAnchor::onItemDestroyed() {
}
void PopupAnchor::onItemWindowChanged() {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem ? this->mItem->window() : nullptr)) {
this->setWindowInternal(window->proxy());
} else {
this->setWindowInternal(nullptr);
+111 -28
View File
@@ -4,39 +4,95 @@
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qjsvalueiterator.h>
#include <qlist.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtversionchecks.h>
#include <qtypes.h>
#include <qvariant.h>
void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->hasActiveIterators = true;
namespace {
struct RecursionPair {
const QJSValue* a;
const QJSValue* b;
const RecursionPair* previous;
};
bool qjsValueStructuralEq(
const QJSValue& a,
const QJSValue& b,
const RecursionPair* activePair = nullptr
) {
if (a.strictlyEquals(b)) return true;
if (!a.isObject() || !b.isObject() || a.isArray() != b.isArray()) return false;
for (const auto* pair = activePair; pair != nullptr; pair = pair->previous) {
if (a.strictlyEquals(*pair->a) || b.strictlyEquals(*pair->b)) return false;
}
const auto nextActivePair = RecursionPair {.a = &a, .b = &b, .previous = activePair};
auto aIter = QJSValueIterator(a);
auto bIter = QJSValueIterator(b);
while (aIter.hasNext()) {
if (!bIter.hasNext()) return false;
aIter.next();
bIter.next();
const auto aName = aIter.name();
const auto bName = bIter.name();
const auto aValue = aIter.value();
if (aName == bName) {
const auto bValue = bIter.value();
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
} else {
if (!b.hasOwnProperty(aName)) return false;
const auto bValue = b.property(aName);
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
}
}
return !bIter.hasNext();
}
} // namespace
bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
auto anyChanges = false;
this->isModifying = true;
this->mValues.reserve(newValues.size());
auto iter = this->mValues.begin();
auto newIter = newValues.begin();
// TODO: cache this
auto getCmpKey = [this](const QVariant& v) {
if (v.canConvert<QVariantMap>()) {
auto vMap = v.value<QVariantMap>();
if (vMap.contains(this->cmpKey)) {
return vMap.value(this->cmpKey);
}
auto comparisonValue = [this](const QJSValue& value) {
if (value.hasProperty(this->cmpKey)) return value.property(this->cmpKey);
return value;
};
auto valueCmp = [&, this](const QJSValue& a, const QJSValue& b) {
if (a.strictlyEquals(b)) return true;
const auto aValue = this->cmpKey.isEmpty() ? a : comparisonValue(a);
const auto bValue = this->cmpKey.isEmpty() ? b : comparisonValue(b);
if (this->mComparisonMode == ObjectComparison::Identity) {
return aValue.strictlyEquals(bValue);
} else {
return qjsValueStructuralEq(aValue, bValue);
}
return v;
};
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
else return a == b;
};
auto eqPredicate = [&](const QVariant& b) {
return [&](const QVariant& a) { return variantCmp(a, b); };
auto eqPredicate = [&](const QJSValue& b) {
return [&](const QJSValue& a) { return valueCmp(a, b); };
};
while (true) {
@@ -49,6 +105,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, endIndex);
this->mValues.erase(iter, this->mValues.end());
this->endRemoveRows();
anyChanges = true;
break;
} else if (iter == this->mValues.end()) {
@@ -59,9 +116,10 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginInsertRows(QModelIndex(), startIndex, endIndex);
this->mValues.append(newValues.sliced(startIndex));
this->endInsertRows();
anyChanges = true;
break;
} else if (!variantCmp(*newIter, *iter)) {
} else if (!valueCmp(*newIter, *iter)) {
auto oldIter = std::find_if(iter, this->mValues.end(), eqPredicate(*newIter));
if (oldIter != this->mValues.end()) {
@@ -81,6 +139,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, index - 1);
iter = this->mValues.erase(startIter, iter);
this->endRemoveRows();
anyChanges = true;
} else {
// Advance iters to capture a whole move sequence as a single operation if possible.
auto oldStartIter = oldIter;
@@ -88,7 +147,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
++oldIter;
++newIter;
} while (oldIter != this->mValues.end() && newIter != newValues.end()
&& variantCmp(*oldIter, *newIter));
&& valueCmp(*oldIter, *newIter));
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto oldStartIndex =
@@ -105,6 +164,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
iter = this->mValues.begin() + (index + len);
this->endMoveRows();
anyChanges = true;
}
} else {
auto startNewIter = newIter;
@@ -130,8 +190,9 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
std::move_backward(iter, this->mValues.end() - len, this->mValues.end());
iter = std::copy(startNewIter, newIter, iter);
this->endInsertRows();
anyChanges = true;
}
} else if (*newIter != *iter) {
} else if (!newIter->strictlyEquals(*iter)) {
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto index = first;
@@ -140,26 +201,42 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
++iter;
++newIter;
++index;
} while (iter != this->mValues.end() && newIter != newValues.end() && *newIter != *iter);
} while (iter != this->mValues.end() && newIter != newValues.end()
&& !newIter->strictlyEquals(*iter));
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(index - 1, 0, QModelIndex()),
{Qt::UserRole}
);
anyChanges = true;
} else {
++iter;
++newIter;
}
}
this->hasActiveIterators = false;
this->isModifying = false;
if (this->stagedValues.has_value()) {
auto values = *this->stagedValues;
this->stagedValues.reset();
anyChanges |= this->updateValuesUnique(values);
}
return anyChanges;
}
void ScriptModel::setValues(const QVariantList& newValues) {
if (newValues == this->mValues) return;
this->updateValuesUnique(newValues);
emit this->valuesChanged();
void ScriptModel::setValues(const QList<QJSValue>& newValues) {
// Re-entrant modification waits for the original modification to complete to avoid mishandled duplicates.
if (this->isModifying) {
this->stagedValues = newValues;
return;
}
auto changed = this->updateValuesUnique(newValues);
if (changed) emit this->valuesChanged();
}
void ScriptModel::setObjectProp(const QString& objectProp) {
@@ -169,6 +246,12 @@ void ScriptModel::setObjectProp(const QString& objectProp) {
emit this->objectPropChanged();
}
void ScriptModel::setComparisonMode(ObjectComparison::Enum comparisonMode) {
if (comparisonMode == this->mComparisonMode) return;
this->mComparisonMode = comparisonMode;
emit this->comparisonModeChanged();
}
qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->mValues.length());
@@ -176,7 +259,7 @@ qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
QVariant ScriptModel::data(const QModelIndex& index, qint32 role) const {
if (role != Qt::UserRole) return QVariant();
return this->mValues.at(index.row());
return this->mValues.at(index.row()).toVariant(QJSValue::RetainJSObjects);
}
QHash<int, QByteArray> ScriptModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; }
+42 -7
View File
@@ -1,10 +1,31 @@
#pragma once
#include <optional>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
///! @@ScriptModel value comparison mode.
namespace ObjectComparison { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum : quint8 {
/// Compare values using strict JavaScript equality.
Identity = 0,
/// Compare records and arrays structurally.
Structure = 1,
};
Q_ENUM_NS(Enum);
} // namespace ObjectComparison
///! QML model reflecting a javascript expression
/// ScriptModel is a QML [Data Model] that generates model operations based on changes
@@ -40,6 +61,7 @@
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
class ScriptModel: public QAbstractListModel {
Q_OBJECT;
// clang-format off
/// The list of values to reflect in the model.
/// > [!WARNING] ScriptModel currently only works with lists of *unique* values.
/// > There must not be any duplicates in the given list, or behavior of the model is undefined.
@@ -66,7 +88,8 @@ class ScriptModel: public QAbstractListModel {
/// > values: [...DesktopEntries.applications.values].sort(...)
/// > }
/// > ```
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
// TODO: use qjsobject for gc/free checks?
Q_PROPERTY(QList<QJSValue> values READ values WRITE setValues NOTIFY valuesChanged);
/// The property that javascript objects passed into the model will be compared with.
///
/// For example, if `objectProp` is `"myprop"` then `{ myprop: "a", other: "y" }` and
@@ -74,21 +97,30 @@ class ScriptModel: public QAbstractListModel {
///
/// Defaults to `""`, meaning no key.
Q_PROPERTY(QString objectProp READ objectProp WRITE setObjectProp NOTIFY objectPropChanged);
/// How values should be compared. Defaults to `ObjectComparison.Structure`.
///
/// Identity based comparison is faster if usable for a given model, and often
/// achievable with @@objectProp.
Q_PROPERTY(ObjectComparison::Enum comparisonMode READ comparisonMode WRITE setComparisonMode NOTIFY comparisonModeChanged);
// clang-format on
QML_ELEMENT;
public:
[[nodiscard]] QVariantList values() const {
[[nodiscard]] QList<QJSValue> values() const {
auto values = this->mValues;
// If not detached, the QML engine will invalidate iterators in updateValuesUnique.
if (this->hasActiveIterators) values.detach();
if (this->isModifying) values.detach();
return values;
}
void setValues(const QVariantList& newValues);
void setValues(const QList<QJSValue>& newValues);
[[nodiscard]] QString objectProp() const { return this->cmpKey; }
void setObjectProp(const QString& objectProp);
[[nodiscard]] ObjectComparison::Enum comparisonMode() const { return this->mComparisonMode; }
void setComparisonMode(ObjectComparison::Enum comparisonMode);
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
@@ -96,11 +128,14 @@ public:
signals:
void valuesChanged();
void objectPropChanged();
void comparisonModeChanged();
private:
QVariantList mValues;
QList<QJSValue> mValues;
QString cmpKey;
bool hasActiveIterators = false;
ObjectComparison::Enum mComparisonMode = ObjectComparison::Structure;
bool isModifying = false;
std::optional<QList<QJSValue>> stagedValues;
void updateValuesUnique(const QVariantList& newValues);
bool updateValuesUnique(const QList<QJSValue>& newValues);
};
+155 -53
View File
@@ -4,10 +4,11 @@
#include <qabstractitemmodeltester.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qjsengine.h>
#include <qjsvalue.h>
#include <qlist.h>
#include <qlogging.h>
#include <qobject.h>
#include <qsignalspy.h>
#include <qstring.h>
#include <qtest.h>
#include <qtestcase.h>
@@ -15,8 +16,6 @@
#include "../scriptmodel.hpp"
using OpList = QList<ModelOperation>;
bool ModelOperation::operator==(const ModelOperation& other) const {
return other.operation == this->operation && other.index == this->index
&& other.length == this->length && other.destIndex == this->destIndex;
@@ -44,15 +43,44 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
return debug;
}
ChangeObserver::ChangeObserver(QAbstractItemModel* model) {
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ChangeObserver::onInsert);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ChangeObserver::onRemove);
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ChangeObserver::onMove);
}
void ChangeObserver::onInsert(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Insert, first, last - first + 1);
}
void ChangeObserver::onRemove(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Remove, first, last - first + 1);
}
void ChangeObserver::onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
this->mOperations
.emplaceBack(ModelOperation::Move, sourceStart, sourceEnd - sourceStart + 1, destStart);
}
// NOLINTNEXTLINE(misc-use-internal-linkage)
QDebug& operator<<(QDebug& debug, const QVariantList& list) {
auto str = QString();
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
QString str;
for (const auto& var: list) {
if (var.canConvert<QChar>()) {
str += var.value<QChar>();
if (var.isString()) {
str += var.toString();
} else {
qFatal() << "QVariantList debug overridden in test";
qFatal() << "QJSValueList debug overridden in test";
}
}
@@ -60,6 +88,18 @@ QDebug& operator<<(QDebug& debug, const QVariantList& list) {
return debug;
}
namespace {
bool qjsValueListsEqual(const QJSValueList& a, const QJSValueList& b) {
if (a.length() != b.length()) return false;
for (auto i = 0; i != a.length(); i++) {
if (!a.at(i).strictlyEquals(b.at(i))) return false;
}
return true;
}
} // namespace
void TestScriptModel::unique_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
@@ -117,65 +157,127 @@ void TestScriptModel::unique() {
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
auto strToVariantList = [](const QString& str) -> QVariantList {
QVariantList list;
auto strToJsValueList = [](const QString& str) -> QJSValueList {
QJSValueList list;
for (auto c: str) {
list.emplace_back(c);
list.emplace_back(QString(c));
}
return list;
};
auto oldlist = strToVariantList(oldstr);
auto newlist = strToVariantList(newstr);
auto oldList = strToJsValueList(oldstr);
auto newList = strToJsValueList(newstr);
auto model = ScriptModel();
ScriptModel model;
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
OpList actualOperations;
auto onInsert = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Insert, first, last - first + 1);
};
auto onRemove = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Remove, first, last - first + 1);
};
auto onMove = [&](const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
actualOperations << ModelOperation(
ModelOperation::Move,
sourceStart,
sourceEnd - sourceStart + 1,
destStart
);
};
QObject::connect(&model, &QAbstractItemModel::rowsInserted, &model, onInsert);
QObject::connect(&model, &QAbstractItemModel::rowsRemoved, &model, onRemove);
QObject::connect(&model, &QAbstractItemModel::rowsMoved, &model, onMove);
model.setValues(oldlist);
QCOMPARE_EQ(model.values(), oldlist);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
actualOperations,
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldlist.length())}})
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
actualOperations.clear();
observer.clear();
model.setValues(newlist);
QCOMPARE_EQ(model.values(), newlist);
QCOMPARE_EQ(actualOperations, operations);
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
}
void TestScriptModel::structuralEquality() {
ScriptModel model;
QJSEngine engine;
auto object = engine.newObject();
object.setProperty("foo", "bar");
object.setProperty("bar", "baz");
object.setProperty("subobject", engine.newObject());
auto replacement = engine.newObject();
replacement.setProperty("subobject", engine.newObject());
replacement.setProperty("bar", "baz");
replacement.setProperty("foo", "bar");
auto oldList = QList<QJSValue> {object};
auto newList = QList<QJSValue> {replacement};
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
observer.clear();
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), OpList());
auto structurallyEquals = [](const QJSValue& a, const QJSValue& b) {
ScriptModel model;
model.setValues({a});
auto observer = ChangeObserver(&model);
model.setValues({b});
return observer.operations().isEmpty();
};
auto different = engine.newObject();
different.setProperty("foo", "bar");
different.setProperty("bar", "baz");
auto differentSubobject = engine.newObject();
differentSubobject.setProperty("value", 1);
different.setProperty("subobject", differentSubobject);
QVERIFY(!structurallyEquals(object, different));
auto cycleA = engine.newObject();
cycleA.setProperty("self", cycleA);
auto cycleB = engine.newObject();
cycleB.setProperty("self", cycleB);
QVERIFY(!structurallyEquals(cycleA, cycleB));
const auto arrayA = engine.evaluate("[1, { value: 'x' }]");
const auto arrayB = engine.evaluate("[1, { value: 'x' }]");
QVERIFY(structurallyEquals(arrayA, arrayB));
auto sparseArrayA = engine.newArray(1);
auto sparseArrayB = engine.newArray(2);
QVERIFY(!structurallyEquals(sparseArrayA, sparseArrayB));
}
void TestScriptModel::comparisonModes() {
QJSEngine engine;
auto makeValue = [&engine]() {
auto value = engine.newObject();
value.setProperty("nested", engine.newObject());
return value;
};
ScriptModel model;
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Structure);
model.setValues({makeValue()});
model.setComparisonMode(ObjectComparison::Identity);
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Identity);
auto observer = ChangeObserver(&model);
model.setValues({makeValue()});
QVERIFY(!observer.operations().isEmpty());
model.setComparisonMode(ObjectComparison::Structure);
observer.clear();
model.setValues({makeValue()});
QCOMPARE_EQ(observer.operations(), OpList());
}
QTEST_MAIN(TestScriptModel);
+27
View File
@@ -1,6 +1,8 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qdebug.h>
#include <qlist.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -26,6 +28,29 @@ struct ModelOperation {
[[nodiscard]] bool operator==(const ModelOperation& other) const;
};
using OpList = QList<ModelOperation>;
class ChangeObserver: public QObject {
public:
explicit ChangeObserver(QAbstractItemModel* model);
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
void clear() { this->mOperations.clear(); }
private:
void onInsert(const QModelIndex& parent, int first, int last);
void onRemove(const QModelIndex& parent, int first, int last);
void onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
);
OpList mOperations;
};
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
class TestScriptModel: public QObject {
@@ -34,4 +59,6 @@ class TestScriptModel: public QObject {
private slots:
static void unique_data(); // NOLINT
static void unique();
static void structuralEquality();
static void comparisonModes();
};
+1
View File
@@ -495,6 +495,7 @@ void FileView::updateWatchedFiles() {
// If inotify events are sent to the watcher after deletion and deleteLater
// isn't used, a use after free in the QML engine will occur.
if (this->watcher) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+1 -1
View File
@@ -204,7 +204,7 @@ class FileView: public QObject {
///
/// > [!NOTE] This works by creating another file with the desired content, and renaming
/// > it over the existing file if successful.
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableAtomicWrites);
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY atomicWritesChanged BINDABLE bindableAtomicWrites);
/// If true (default), read or write errors will be printed to the quickshell logs.
/// If false, all known errors will not be printed.
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
+67 -154
View File
@@ -1,181 +1,94 @@
#include "ipc.hpp"
#include <cstring>
#include <utility>
#include <qcolor.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtypes.h>
#include <qvariant.h>
namespace qs::io::ipc {
const VoidIpcType VoidIpcType::INSTANCE {};
const StringIpcType StringIpcType::INSTANCE {};
const IntIpcType IntIpcType::INSTANCE {};
const BoolIpcType BoolIpcType::INSTANCE {};
const DoubleIpcType DoubleIpcType::INSTANCE {};
const ColorIpcType ColorIpcType::INSTANCE {};
bool IpcValueSlot::isSupported(const QMetaType& type) { return IpcValueSlot::typeName(type); }
void* IpcType::copyStorage(const void* data) const {
auto* storage = this->createStorage();
memcpy(storage, data, this->size());
return storage;
}
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
return nullptr;
}
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
this->mType = other.mType;
this->storage = other.storage;
other.mType = nullptr;
other.storage = nullptr;
return *this;
}
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
const IpcType* IpcTypeSlot::type() const { return this->mType; }
void* IpcTypeSlot::get() {
if (this->storage == nullptr) {
this->storage = this->mType->createStorage();
}
return this->storage;
}
QGenericArgument IpcTypeSlot::asGenericArgument() {
if (this->mType) {
return QGenericArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericArgument();
const char* IpcValueSlot::typeName(const QMetaType& type) {
switch (type.id()) {
case QMetaType::Void: return "void";
case QMetaType::QString: return "string";
case QMetaType::Int: return "int";
case QMetaType::Bool: return "bool";
case QMetaType::Double: return "real";
case QMetaType::QColor: return "color";
default: return nullptr;
}
}
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
if (this->mType) {
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericReturnArgument();
const char* IpcValueSlot::name() const {
return this->mValue.isValid() ? IpcValueSlot::typeName(this->mValue.metaType()) : "void";
}
bool IpcValueSlot::isVoid() const { return !this->mValue.isValid(); }
const QVariant& IpcValueSlot::value() const { return this->mValue; }
bool IpcValueSlot::setString(const QString& string) {
switch (this->mValue.typeId()) {
case QMetaType::QString: this->mValue = QVariant::fromValue(string); return true;
case QMetaType::Int: {
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::Bool: {
if (string == "true" || string == "false") {
this->mValue = QVariant::fromValue(string == "true");
return true;
}
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value != 0);
return ok;
}
case QMetaType::Double: {
auto ok = false;
auto value = string.toDouble(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::QColor: {
auto value = QColor::fromString(string);
if (!value.isValid() && !string.startsWith('#')) value = QColor::fromString('#' % string);
if (value.isValid()) this->mValue = QVariant::fromValue(value);
return value.isValid();
}
default: return false;
}
}
void IpcTypeSlot::replace(void* value) {
if (this->storage != nullptr) {
this->mType->destroyStorage(this->storage);
void IpcValueSlot::setValue(QVariant value) { this->mValue = std::move(value); }
QString IpcValueSlot::toString() const {
switch (this->mValue.typeId()) {
case QMetaType::UnknownType: return "";
case QMetaType::QString: return this->mValue.value<QString>();
case QMetaType::Int: return QString::number(this->mValue.value<int>());
case QMetaType::Bool: return this->mValue.value<bool>() ? "true" : "false";
case QMetaType::Double: return QString::number(this->mValue.value<double>());
case QMetaType::QColor: return this->mValue.value<QColor>().name(QColor::HexArgb);
default: return "";
}
this->storage = value;
}
void IpcTypeSlot::replace(const QVariant& value) {
this->replace(this->mType->copyStorage(value.constData()));
QGenericArgument IpcValueSlot::asGenericArgument() const {
return QGenericArgument(this->mValue.metaType().name(), this->mValue.constData());
}
const char* VoidIpcType::name() const { return "void"; }
const char* VoidIpcType::genericArgumentName() const { return "void"; }
qsizetype VoidIpcType::size() const { return 0; }
// string
const char* StringIpcType::name() const { return "string"; }
const char* StringIpcType::genericArgumentName() const { return "QString"; }
qsizetype StringIpcType::size() const { return sizeof(QString); }
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
void* StringIpcType::createStorage() const { return new QString(); }
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
// int
const char* IntIpcType::name() const { return "int"; }
const char* IntIpcType::genericArgumentName() const { return "int"; }
qsizetype IntIpcType::size() const { return sizeof(int); }
void* IntIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toInt(&ok);
return ok ? new int(v) : nullptr;
QGenericReturnArgument IpcValueSlot::asGenericReturnArgument() {
if (!this->mValue.isValid()) return QGenericReturnArgument();
return QGenericReturnArgument(this->mValue.metaType().name(), this->mValue.data());
}
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
void* IntIpcType::createStorage() const { return new int(); }
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
// bool
const char* BoolIpcType::name() const { return "bool"; }
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
qsizetype BoolIpcType::size() const { return sizeof(bool); }
void* BoolIpcType::fromString(const QString& string) const {
if (string == "true") return new bool(true);
if (string == "false") return new bool(false);
auto isInt = false;
auto iv = string.toInt(&isInt);
return isInt ? new bool(iv != 0) : nullptr;
}
QString BoolIpcType::toString(void* slot) const {
return *static_cast<bool*>(slot) ? "true" : "false";
}
void* BoolIpcType::createStorage() const { return new bool(); }
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
// double
const char* DoubleIpcType::name() const { return "real"; }
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
qsizetype DoubleIpcType::size() const { return sizeof(double); }
void* DoubleIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toDouble(&ok);
return ok ? new double(v) : nullptr;
}
QString DoubleIpcType::toString(void* slot) const {
return QString::number(*static_cast<double*>(slot));
}
void* DoubleIpcType::createStorage() const { return new double(); }
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
// color
const char* ColorIpcType::name() const { return "color"; }
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
void* ColorIpcType::fromString(const QString& string) const {
auto color = QColor::fromString(string);
if (!color.isValid() && !string.startsWith('#')) {
color = QColor::fromString('#' % string);
}
return color.isValid() ? new QColor(color) : nullptr;
}
QString ColorIpcType::toString(void* slot) const {
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
}
void* ColorIpcType::createStorage() const { return new bool(); }
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
QString WireFunctionDefinition::toString() const {
QString paramString;
for (const auto& [name, type]: this->arguments) {
+15 -110
View File
@@ -1,130 +1,35 @@
#pragma once
#include <qcontainerfwd.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "../ipc/ipc.hpp"
namespace qs::io::ipc {
class IpcTypeSlot;
class IpcType {
class IpcValueSlot {
public:
IpcType() = default;
virtual ~IpcType() = default;
IpcType(const IpcType&) = default;
IpcType(IpcType&&) = default;
IpcType& operator=(const IpcType&) = default;
IpcType& operator=(IpcType&&) = default;
IpcValueSlot() = default;
explicit IpcValueSlot(QMetaType type): mValue(type) {}
[[nodiscard]] virtual const char* name() const = 0;
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
[[nodiscard]] virtual qsizetype size() const = 0;
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
virtual void destroyStorage(void* /*slot*/) const {}
void* copyStorage(const void* data) const;
[[nodiscard]] static bool isSupported(const QMetaType& type);
[[nodiscard]] static const char* typeName(const QMetaType& type);
[[nodiscard]] const char* name() const;
[[nodiscard]] bool isVoid() const;
[[nodiscard]] const QVariant& value() const;
static const IpcType* ipcType(const QMetaType& metaType);
};
bool setString(const QString& string);
void setValue(QVariant value);
[[nodiscard]] QString toString() const;
class IpcTypeSlot {
public:
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
~IpcTypeSlot();
Q_DISABLE_COPY(IpcTypeSlot);
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
[[nodiscard]] const IpcType* type() const;
[[nodiscard]] void* get();
[[nodiscard]] QGenericArgument asGenericArgument();
[[nodiscard]] QGenericArgument asGenericArgument() const;
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
void replace(void* value);
void replace(const QVariant& value);
private:
const IpcType* mType = nullptr;
void* storage = nullptr;
};
class VoidIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
static const VoidIpcType INSTANCE;
};
class StringIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const StringIpcType INSTANCE;
};
class IntIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const IntIpcType INSTANCE;
};
class BoolIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const BoolIpcType INSTANCE;
};
class DoubleIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const DoubleIpcType INSTANCE;
};
class ColorIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const ColorIpcType INSTANCE;
QVariant mValue;
};
struct WireFunctionDefinition {
+3 -3
View File
@@ -173,7 +173,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
func->invoke(handler, storage);
resp << Completed {
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
.isVoid = func->returnType.isVoid(),
.returnValue = storage.getReturnStr(),
};
} else {
@@ -269,11 +269,11 @@ void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
return;
}
auto slot = IpcTypeSlot(prop->type);
auto slot = IpcValueSlot(prop->type);
prop->read(handler, slot);
resp << PropertyValue {
.value = slot.type()->toString(slot.get()),
.value = slot.toString(),
};
} else {
conn->respond(StringCallResponse(NoCurrentGeneration()));
+36 -39
View File
@@ -34,9 +34,8 @@ bool IpcFunction::resolve(QString& error) {
for (auto i = 0; i < this->method.parameterCount(); i++) {
const auto& metaType = this->method.parameterMetaType(i);
const auto* type = IpcType::ipcType(metaType);
if (type == nullptr) {
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
.arg(i + 1)
.arg(this->method.parameterNames().value(i))
@@ -45,23 +44,20 @@ bool IpcFunction::resolve(QString& error) {
return false;
}
this->argumentTypes.append(type);
this->argumentTypes.emplaceBack(metaType);
}
const auto& metaType = this->method.returnMetaType();
const auto* type = IpcType::ipcType(metaType);
auto metaType = this->method.returnMetaType();
if (type == nullptr) {
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) metaType = QMetaType::fromType<void>();
if (type == nullptr) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
this->returnType = type;
this->returnType = IpcValueSlot(metaType);
return true;
}
@@ -92,52 +88,53 @@ QString IpcFunction::toString() const {
QString paramString;
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i).name();
if (i + 1 != this->argumentTypes.length()) {
paramString += ", ";
}
}
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType.name();
}
WireFunctionDefinition IpcFunction::wireDef() const {
WireFunctionDefinition wire;
wire.name = this->method.name();
wire.returnType = this->returnType->name();
wire.returnType = this->returnType.name();
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i).name());
}
return wire;
}
bool IpcProperty::resolve(QString& error) {
this->type = IpcType::ipcType(this->property.metaType());
const auto& metaType = this->property.metaType();
if (!this->type) {
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type %1 cannot be used across IPC.").arg(metaType.name());
return false;
}
this->type = metaType;
return true;
}
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
slot.replace(this->property.read(target));
void IpcProperty::read(QObject* target, IpcValueSlot& slot) const {
slot.setValue(this->property.read(target));
}
QString IpcProperty::toString() const {
return QString("property ") % this->property.name() % ": " % this->type->name();
return QString("property ") % this->property.name() % ": " % IpcValueSlot::typeName(this->type);
}
WirePropertyDefinition IpcProperty::wireDef() const {
WirePropertyDefinition wire;
wire.name = this->property.name();
wire.type = this->type->name();
wire.type = IpcValueSlot::typeName(this->type);
return wire;
}
@@ -217,21 +214,15 @@ IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function
}
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
auto& slot = this->argumentSlots.at(i);
auto* data = slot.type()->fromString(value);
slot.replace(data);
return data != nullptr;
return this->argumentSlots.at(i).setString(value);
}
QString IpcCallStorage::getReturnStr() {
return this->returnSlot.type()->toString(this->returnSlot.get());
}
QString IpcCallStorage::getReturnStr() { return this->returnSlot.toString(); }
IpcHandler::~IpcHandler() {
if (this->registeredState.enabled) {
this->targetState.enabled = false;
this->updateRegistration(true);
this->updateRegistration();
}
}
@@ -310,28 +301,34 @@ void IpcHandler::onSignalTriggered(const QString& signal, const QString& value)
-> triggered(this->registeredState.target, signal, value);
}
void IpcHandler::updateRegistration(bool destroying) {
void IpcHandler::updateRegistration() {
if (!this->complete) return;
auto* generation = EngineGeneration::findObjectGeneration(this);
IpcHandlerRegistry* registry = nullptr;
if (this->registeredState.enabled) {
registry = this->registry.get();
} else {
auto* generation = EngineGeneration::findObjectGeneration(this);
if (!generation) {
if (!destroying) {
if (!generation) {
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
return;
}
return;
registry = IpcHandlerRegistry::forGeneration(generation);
}
auto* registry = IpcHandlerRegistry::forGeneration(generation);
if (!registry) return;
if (this->registeredState.enabled) {
registry->deregisterHandler(this);
this->registry = nullptr;
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
}
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
registry->registerHandler(this);
this->registry = registry;
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
}
}
+16 -9
View File
@@ -11,6 +11,7 @@
#include <qmetaobject.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qpointer.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qtclasshelpermacros.h>
@@ -37,8 +38,8 @@ public:
[[nodiscard]] WireFunctionDefinition wireDef() const;
QMetaMethod method;
QVector<const IpcType*> argumentTypes;
const IpcType* returnType = nullptr;
QVector<IpcValueSlot> argumentTypes;
IpcValueSlot returnType;
};
class IpcCallStorage {
@@ -49,8 +50,8 @@ public:
[[nodiscard]] QString getReturnStr();
private:
std::vector<IpcTypeSlot> argumentSlots;
IpcTypeSlot returnSlot;
std::vector<IpcValueSlot> argumentSlots;
IpcValueSlot returnSlot;
friend class IpcFunction;
};
@@ -60,13 +61,13 @@ public:
explicit IpcProperty(QMetaProperty property): property(property) {}
bool resolve(QString& error);
void read(QObject* target, IpcTypeSlot& slot) const;
void read(QObject* target, IpcValueSlot& slot) const;
[[nodiscard]] QString toString() const;
[[nodiscard]] WirePropertyDefinition wireDef() const;
QMetaProperty property;
const IpcType* type = nullptr;
QMetaType type;
};
class IpcSignalListener: public QObject {
@@ -185,7 +186,7 @@ class IpcHandlerRegistry;
///
/// function getRadius(): int { return rect.radius; }
///
/// signal radiusChanged(newRadius: int);
/// signal radiusChanged(newRadius: int);
/// }
/// }
/// ```
@@ -254,7 +255,7 @@ public slots:
void onSignalTriggered(const QString& signal, const QString& value) const;
private:
void updateRegistration(bool destroying = false);
void updateRegistration();
struct RegistrationState {
explicit RegistrationState(bool enabled = false): enabled(enabled) {}
@@ -271,10 +272,16 @@ private:
QHash<QString, IpcProperty> propertyMap;
QHash<QString, IpcSignal> signalMap;
QPointer<IpcHandlerRegistry> registry;
friend class IpcHandlerRegistry;
};
class IpcHandlerRegistry: public EngineGenerationExt {
class IpcHandlerRegistry
: public QObject
, public EngineGenerationExt {
Q_OBJECT;
public:
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
+5 -5
View File
@@ -225,15 +225,15 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
const auto& jsonValue = array.at(i);
if (jsonValue.isObject()) {
if (isNew) {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
} else {
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
currentValue->setParent(this);
this->createdObjects.push_back(currentValue);
} else {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
}
this->deserializeRec(
+11 -3
View File
@@ -105,11 +105,19 @@ IpcClient::IpcClient(const QString& path) {
bool IpcClient::isConnected() const { return this->socket.isValid(); }
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
void IpcClient::waitForDisconnected() {
this->waitingForDisconnect = true;
this->socket.waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
void IpcClient::kill() {
this->sendMessage(IpcCommand(IpcKillCommand()));
this->waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) const {
if (this->waitingForDisconnect && error == QLocalSocket::PeerClosedError) return;
qCCritical(logIpc) << "Socket Error" << error;
}
+4 -1
View File
@@ -216,7 +216,10 @@ signals:
void disconnected();
private slots:
static void onError(QLocalSocket::LocalSocketError error);
void onError(QLocalSocket::LocalSocketError error) const;
private:
bool waitingForDisconnect = false;
};
} // namespace qs::ipc
+51 -39
View File
@@ -164,7 +164,12 @@ void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
});
};
int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallback = false) {
int selectInstance(
CommandState& cmd,
InstanceLockInfo* instance,
bool deadFallback = false,
bool logHelp = true
) {
auto* basePath = QsPaths::instance()->baseRunDir();
if (!basePath) return -1;
@@ -173,7 +178,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
if (cmd.instance.pid != -1) {
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
if (!QsPaths::checkLock(path, instance, deadFallback)) {
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
if (logHelp) qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
return -1;
}
} else if (!cmd.instance.id->isEmpty()) {
@@ -196,39 +201,43 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
if (logHelp) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
qCInfo(logBare) << "Some instances on other displays match:";
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace()
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
}
}
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
}
}
}
return -1;
} else if (instances.length() != 1) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
if (logHelp) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
for (auto& instance: instances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
<< (instance.pid == -1 ? "(dead)" : "");
for (auto& instance: instances) {
qCInfo(logBare).noquote()
<< " -" << instance.instance.instanceId << (instance.pid == -1 ? "(dead)" : "");
}
}
return -1;
@@ -259,26 +268,29 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
);
if (instances.isEmpty()) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath
<< " present on the current display" << getDisplayConnection();
if (logHelp) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath
<< " present on the current display" << getDisplayConnection();
qCInfo(logBare) << "Some instances on other displays match:";
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace()
<< " - " << instance.instance.instanceId << " (" << instance.instance.display << ')';
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
return -1;
}
@@ -461,7 +473,7 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
{
InstanceLockInfo info;
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
if (cmd.misc.noDuplicate && selectInstance(cmd, &info, false, false) == 0) {
qCInfo(logBare) << "An instance of this configuration is already running.";
return 0;
}
+17 -10
View File
@@ -27,20 +27,28 @@ namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
#if CRASH_HANDLER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
auto dumpPid = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
if (!lastInfoFdStr.isEmpty()) {
auto lastInfoFd = lastInfoFdStr.toInt();
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
RelaunchInfo info;
{
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
}
file.seek(0);
auto ds = QDataStream(&file);
ds >> info;
}
file.seek(0);
auto ds = QDataStream(&file);
RelaunchInfo info;
ds >> info;
qunsetenv("__QUICKSHELL_CRASH_INFO_FD");
qunsetenv("__QUICKSHELL_CRASH_DUMP_PID");
qunsetenv("__QUICKSHELL_CRASH_SIGNAL");
LogManager::init(
!info.noColor,
@@ -50,8 +58,7 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
info.logRules
);
qCritical().nospace() << "Quickshell has crashed under pid "
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
qCritical().nospace() << "Quickshell has crashed under pid " << dumpPid
<< " (Coredumps will be available under that pid.)";
qCritical() << "Further crash information is stored under"
+4 -2
View File
@@ -72,9 +72,10 @@ void NMNetwork::addSettings(NMSettings* settings) {
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
// Deletes `this`
emit this->settingsRemoved(settings);
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
@@ -240,8 +241,9 @@ void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
emit this->apRemoved(ap);
this->updateReferenceAp();
// Deletes `this`
emit this->apRemoved(ap);
}
};
// clang-format off
+2 -2
View File
@@ -325,8 +325,8 @@ public:
void setLoopState(MprisLoopState::Enum loopState);
[[nodiscard]] qreal rate() const { return this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bMinRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bMaxRate; }
void setRate(qreal rate);
[[nodiscard]] bool shuffle() const { return this->bShuffle; }
+15 -3
View File
@@ -8,6 +8,8 @@
#include <qdbusservicewatcher.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobjectdefs.h>
#include <qqmlengine.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -192,19 +194,29 @@ uint NotificationServer::Notify(
notification->updateProperties(appName, appIcon, summary, body, actions, hints, expireTimeout);
auto id = notification->id();
if (!old) {
emit this->notification(notification);
if (!notification->isTracked()) {
emit this->NotificationClosed(notification->id(), notification->closeReason());
auto closeReason = notification->closeReason();
// Emit NotificationClosed after Notify returns
QMetaObject::invokeMethod(
this,
[this, id, closeReason]() { emit this->NotificationClosed(id, closeReason); },
Qt::QueuedConnection
);
delete notification;
} else {
this->idMap.insert(notification->id(), notification);
this->idMap.insert(id, notification);
this->mNotifications.insertObject(notification);
}
}
return notification->id();
return id;
}
} // namespace qs::service::notifications
+23 -2
View File
@@ -245,13 +245,14 @@ void PwPeakStream::handleProcess() {
if (!this->formatReady || this->stream == nullptr) return;
auto* buffer = pw_stream_dequeue_buffer(this->stream);
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
if (buffer == nullptr) {
qCWarning(logPeak) << "Peak monitor ran out of buffers.";
return;
}
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
auto* spaBuffer = buffer->buffer;
if (spaBuffer == nullptr || spaBuffer->n_datas < 1) {
return;
@@ -277,7 +278,27 @@ void PwPeakStream::handleProcess() {
QVector<float> volumes;
if (auto* audioData = dynamic_cast<PwNodeBoundAudio*>(this->node->boundData)) {
if (!this->node->shouldUseDevice()) volumes = audioData->volumes();
// Device volumes don't require inverse scaling
if (!this->node->shouldUseDevice()) {
const auto& nchannels = audioData->channels();
const auto& nvolumes = audioData->volumes();
for (const auto channel: this->monitor->mChannels) {
for (auto i = 0; i != nchannels.length(); i++) {
if (nchannels[i] == channel) {
volumes.push_back(nvolumes[i]);
break;
}
}
}
if (volumes.length() != channelCount) {
qCCritical(logPeak) << this->node
<< "is missing channels present in capture stream. Node channels:"
<< nchannels << "Stream channels:" << this->monitor->mChannels;
return;
}
}
}
this->channelPeaks.fill(0.0f, channelCount);
+1 -1
View File
@@ -195,7 +195,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniIconPixmapList, bAttentionIconPixmaps);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bAttentionMovieName);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniTooltip, bTooltip);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu, &StatusNotifierItem::onlyMenuChanged);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QDBusObjectPath, bMenuPath);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bHasMenu, &StatusNotifierItem::hasMenuChanged);
+1 -1
View File
@@ -105,7 +105,7 @@ if (WAYLAND_SESSION_LOCK)
endif()
if (WAYLAND_TOPLEVEL_MANAGEMENT)
add_subdirectory(toplevel_management)
add_subdirectory(toplevel)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement)
endif()
-1
View File
@@ -799,7 +799,6 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
if (plane.fd < 0) {
qCritical(logDmabuf) << "Failed to get gbm_bo fd for plane" << i << qt_error_string(plane.fd);
params.destroy();
gbm_bo_destroy(bo);
return nullptr;
}
+3 -4
View File
@@ -24,7 +24,7 @@
#include "../../../core/logcat.hpp"
#include "../../../core/model.hpp"
#include "../../../core/qmlscreen.hpp"
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "hyprland_toplevel.hpp"
#include "monitor.hpp"
#include "toplevel_mapping.hpp"
@@ -139,11 +139,10 @@ void HyprlandIpc::eventSocketReady() {
}
void HyprlandIpc::toplevelAddressed(
wayland::toplevel_management::impl::ToplevelHandle* handle,
wayland::toplevel::wlr::ToplevelHandle* handle,
quint64 address
) {
auto* waylandToplevel =
wayland::toplevel_management::ToplevelManager::instance()->forImpl(handle);
auto* waylandToplevel = wayland::toplevel::ToplevelManager::instance()->forImpl(handle);
if (!waylandToplevel) return;
+2 -5
View File
@@ -15,7 +15,7 @@
#include "../../../core/model.hpp"
#include "../../../core/qmlscreen.hpp"
#include "../../../core/streamreader.hpp"
#include "../../../wayland/toplevel_management/handle.hpp"
#include "../../../wayland/toplevel/wlr_toplevel.hpp"
namespace qs::hyprland::ipc {
@@ -128,10 +128,7 @@ private slots:
void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
void eventSocketReady();
void toplevelAddressed(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
quint64 address
);
void toplevelAddressed(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
void onFocusedMonitorDestroyed();
@@ -6,12 +6,12 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../toplevel_management/qml.hpp"
#include "../../toplevel/qml.hpp"
#include "connection.hpp"
#include "toplevel_mapping.hpp"
#include "workspace.hpp"
using namespace qs::wayland::toplevel_management;
using namespace qs::wayland::toplevel;
namespace qs::hyprland::ipc {
@@ -112,7 +112,7 @@ Toplevel* HyprlandToplevel::waylandHandle() {
return ToplevelManager::instance()->forImpl(this->mWaylandHandle);
}
void HyprlandToplevel::setWaylandHandle(impl::ToplevelHandle* handle) {
void HyprlandToplevel::setWaylandHandle(wlr::ToplevelHandle* handle) {
if (this->mWaylandHandle == handle) return;
if (this->mWaylandHandle) {
QObject::disconnect(this->mWaylandHandle, nullptr, this, nullptr);
@@ -7,8 +7,8 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel_management/qml.hpp"
#include "../../toplevel/qml.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "connection.hpp"
namespace qs::hyprland::ipc {
@@ -30,7 +30,7 @@ class HyprlandToplevel: public QObject {
/// Will be null until the address is reported
Q_PROPERTY(HyprlandToplevel* handle READ hyprlandHandle NOTIFY hyprlandHandleChanged);
/// The wayland toplevel handle. Will be null intil the address is reported
Q_PROPERTY(qs::wayland::toplevel_management::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
Q_PROPERTY(qs::wayland::toplevel::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
/// The title of the toplevel
Q_PROPERTY(QString title READ default NOTIFY titleChanged BINDABLE bindableTitle);
/// Whether the toplevel is active or not
@@ -53,7 +53,7 @@ public:
/// When invoked from HyprlandIpc, reacting to Hyprland's IPC events.
explicit HyprlandToplevel(HyprlandIpc* ipc);
/// When attached from a Toplevel
explicit HyprlandToplevel(HyprlandIpc* ipc, qs::wayland::toplevel_management::Toplevel* toplevel);
explicit HyprlandToplevel(HyprlandIpc* ipc, qs::wayland::toplevel::Toplevel* toplevel);
static HyprlandToplevel* qmlAttachedProperties(QObject* object);
@@ -69,8 +69,8 @@ public:
[[nodiscard]] HyprlandToplevel* hyprlandHandle() { return this->mHyprlandHandle; }
void setHyprlandHandle(HyprlandToplevel* handle);
[[nodiscard]] wayland::toplevel_management::Toplevel* waylandHandle();
void setWaylandHandle(wayland::toplevel_management::impl::ToplevelHandle* handle);
[[nodiscard]] wayland::toplevel::Toplevel* waylandHandle();
void setWaylandHandle(wayland::toplevel::wlr::ToplevelHandle* handle);
// clang-format on
[[nodiscard]] QBindable<QString> bindableTitle() { return &this->bTitle; }
@@ -105,7 +105,7 @@ private:
quint64 mAddress = 0;
HyprlandIpc* ipc;
qs::wayland::toplevel_management::impl::ToplevelHandle* mWaylandHandle = nullptr;
qs::wayland::toplevel::wlr::ToplevelHandle* mWaylandHandle = nullptr;
HyprlandToplevel* mHyprlandHandle = nullptr;
// clang-format off
@@ -6,9 +6,9 @@
#include <qtypes.h>
#include <qwaylandclientextension.h>
#include "../../toplevel_management/manager.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
using namespace qs::wayland::toplevel_management::impl;
using namespace qs::wayland::toplevel::wlr;
namespace qs::hyprland::ipc {
+8 -12
View File
@@ -8,7 +8,7 @@
#include <qwayland-hyprland-toplevel-mapping-v1.h>
#include <qwaylandclientextension.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "wayland-hyprland-toplevel-mapping-v1-client-protocol.h"
namespace qs::hyprland::ipc {
@@ -16,7 +16,7 @@ namespace qs::hyprland::ipc {
class HyprlandToplevelMappingHandle: QtWayland::hyprland_toplevel_window_mapping_handle_v1 {
public:
explicit HyprlandToplevelMappingHandle(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
qs::wayland::toplevel::wlr::ToplevelHandle* handle,
::hyprland_toplevel_window_mapping_handle_v1* mapping
)
: QtWayland::hyprland_toplevel_window_mapping_handle_v1(mapping)
@@ -34,7 +34,7 @@ protected:
void hyprland_toplevel_window_mapping_handle_v1_failed() override;
private:
qs::wayland::toplevel_management::impl::ToplevelHandle* handle;
qs::wayland::toplevel::wlr::ToplevelHandle* handle;
};
class HyprlandToplevelMappingManager
@@ -48,22 +48,18 @@ public:
static HyprlandToplevelMappingManager* instance();
[[nodiscard]] quint64
getToplevelAddress(qs::wayland::toplevel_management::impl::ToplevelHandle* handle) const;
getToplevelAddress(qs::wayland::toplevel::wlr::ToplevelHandle* handle) const;
signals:
void toplevelAddressed(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
quint64 address
);
void toplevelAddressed(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
private slots:
void onToplevelReady(qs::wayland::toplevel_management::impl::ToplevelHandle* handle);
void onToplevelReady(qs::wayland::toplevel::wlr::ToplevelHandle* handle);
void onToplevelDestroyed(QObject* object);
private:
void
assignAddress(qs::wayland::toplevel_management::impl::ToplevelHandle* handle, quint64 address);
QHash<wayland::toplevel_management::impl::ToplevelHandle*, quint64> addresses;
void assignAddress(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
QHash<wayland::toplevel::wlr::ToplevelHandle*, quint64> addresses;
friend class HyprlandToplevelMappingHandle;
};
+1 -1
View File
@@ -3,7 +3,7 @@ description = "Wayland specific Quickshell types"
headers = [
"wlr_layershell/wlr_layershell.hpp",
"session_lock.hpp",
"toplevel_management/qml.hpp",
"toplevel/qml.hpp",
"screencopy/view.hpp",
"idle_inhibit/inhibitor.hpp",
"idle_notify/monitor.hpp",
@@ -9,7 +9,7 @@
#include <wayland-hyprland-toplevel-export-v1-client-protocol.h>
#include "../../../core/logcat.hpp"
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "../manager.hpp"
#include "hyprland_screencopy_p.hpp"
@@ -29,7 +29,7 @@ HyprlandScreencopyManager* HyprlandScreencopyManager::instance() {
}
ScreencopyContext* HyprlandScreencopyManager::captureToplevel(
toplevel_management::impl::ToplevelHandle* handle,
toplevel::wlr::ToplevelHandle* handle,
bool paintCursors
) {
return new HyprlandScreencopyContext(this, handle, paintCursors);
@@ -37,7 +37,7 @@ ScreencopyContext* HyprlandScreencopyManager::captureToplevel(
HyprlandScreencopyContext::HyprlandScreencopyContext(
HyprlandScreencopyManager* manager,
toplevel_management::impl::ToplevelHandle* handle,
toplevel::wlr::ToplevelHandle* handle,
bool paintCursors
)
: manager(manager)
@@ -3,7 +3,7 @@
#include <qwayland-hyprland-toplevel-export-v1.h>
#include <qwaylandclientextension.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "../manager.hpp"
namespace qs::wayland::screencopy::hyprland {
@@ -12,8 +12,7 @@ class HyprlandScreencopyManager
: public QWaylandClientExtensionTemplate<HyprlandScreencopyManager>
, public QtWayland::hyprland_toplevel_export_manager_v1 {
public:
ScreencopyContext*
captureToplevel(toplevel_management::impl::ToplevelHandle* handle, bool paintCursors);
ScreencopyContext* captureToplevel(toplevel::wlr::ToplevelHandle* handle, bool paintCursors);
static HyprlandScreencopyManager* instance();
@@ -3,7 +3,7 @@
#include <qtclasshelpermacros.h>
#include <qwayland-hyprland-toplevel-export-v1.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "../manager.hpp"
namespace qs::wayland::screencopy::hyprland {
@@ -16,7 +16,7 @@ class HyprlandScreencopyContext
public:
explicit HyprlandScreencopyContext(
HyprlandScreencopyManager* manager,
toplevel_management::impl::ToplevelHandle* handle,
toplevel::wlr::ToplevelHandle* handle,
bool paintCursors
);
@@ -43,7 +43,7 @@ private:
buffer::WlBufferRequest request;
bool copiedFirstFrame = false;
toplevel_management::impl::ToplevelHandle* handle;
toplevel::wlr::ToplevelHandle* handle;
bool paintCursors;
};
+2 -2
View File
@@ -17,7 +17,7 @@
#endif
#if SCREENCOPY_HYPRLAND_TOPLEVEL
#include "../toplevel_management/qml.hpp"
#include "../toplevel/qml.hpp"
#include "hyprland_screencopy/hyprland_screencopy.hpp"
#endif
@@ -42,7 +42,7 @@ ScreencopyContext* ScreencopyManager::createContext(QObject* object, bool paintC
}
#endif
#if SCREENCOPY_HYPRLAND_TOPLEVEL
} else if (auto* toplevel = qobject_cast<toplevel_management::Toplevel*>(object)) {
} else if (auto* toplevel = qobject_cast<toplevel::Toplevel*>(object)) {
auto* manager = hyprland::HyprlandScreencopyManager::instance();
if (manager->isActive()) {
return manager->captureToplevel(toplevel->implHandle(), paintCursors);
+12 -4
View File
@@ -19,6 +19,7 @@
#include "../core/qmlglobal.hpp"
#include "../core/qmlscreen.hpp"
#include "../core/reload.hpp"
#include "../window/proxywindow.hpp"
#include "session_lock/session_lock.hpp"
void WlSessionLock::onReload(QObject* oldInstance) {
@@ -55,9 +56,12 @@ void WlSessionLock::updateSurfaces(bool show, WlSessionLock* old) {
auto screens = QGuiApplication::screens();
screens.removeIf([](QScreen* screen) {
if (dynamic_cast<QtWaylandClient::QWaylandScreen*>(screen->handle()) == nullptr) {
auto* waylandScreen = dynamic_cast<QtWaylandClient::QWaylandScreen*>(screen->handle());
if (waylandScreen == nullptr || waylandScreen->isPlaceholder()
|| waylandScreen->output() == nullptr)
{
qDebug() << "Not creating lock surface for screen" << screen
<< "as it is not backed by a wayland screen.";
<< "as it is not backed by a valid wayland output.";
return true;
}
@@ -206,6 +210,7 @@ WlSessionLockSurface::WlSessionLockSurface(QObject* parent)
WlSessionLockSurface::~WlSessionLockSurface() {
if (this->window != nullptr) {
this->window->destroy();
this->window->deleteLater();
}
}
@@ -216,7 +221,7 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
}
if (this->window == nullptr) {
this->window = new QQuickWindow();
this->window = new QsQuickWindowBase();
// needed for vulkan dmabuf import, qt ignores these if not applicable
auto graphicsConfig = this->window->graphicsConfiguration();
@@ -273,7 +278,10 @@ void WlSessionLockSurface::show() {
QQuickItem* WlSessionLockSurface::contentItem() const { return this->mContentItem; }
bool WlSessionLockSurface::isVisible() const { return this->window->isVisible(); }
bool WlSessionLockSurface::isVisible() const {
if (this->window == nullptr) return false;
return this->window->isVisible();
}
qint32 WlSessionLockSurface::width() const {
if (this->window == nullptr) return 0;
+1
View File
@@ -38,6 +38,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla
}
QSWaylandSessionLockSurface::~QSWaylandSessionLockSurface() {
if (this->object() == nullptr) return;
if (this->ext != nullptr) this->ext->surface = nullptr;
this->destroy();
}
@@ -1,6 +1,5 @@
qt_add_library(quickshell-wayland-toplevel-management STATIC
manager.cpp
handle.cpp
wlr_toplevel.cpp
qml.cpp
)
@@ -10,23 +10,22 @@
#include "../../core/util.hpp"
#include "../../window/proxywindow.hpp"
#include "../output_tracking.hpp"
#include "handle.hpp"
#include "manager.hpp"
#include "wlr_toplevel.hpp"
namespace qs::wayland::toplevel_management {
namespace qs::wayland::toplevel {
Toplevel::Toplevel(impl::ToplevelHandle* handle, QObject* parent): QObject(parent), handle(handle) {
Toplevel::Toplevel(wlr::ToplevelHandle* handle, QObject* parent): QObject(parent), handle(handle) {
// clang-format off
QObject::connect(handle, &impl::ToplevelHandle::closed, this, &Toplevel::onClosed);
QObject::connect(handle, &impl::ToplevelHandle::appIdChanged, this, &Toplevel::appIdChanged);
QObject::connect(handle, &impl::ToplevelHandle::titleChanged, this, &Toplevel::titleChanged);
QObject::connect(handle, &impl::ToplevelHandle::parentChanged, this, &Toplevel::parentChanged);
QObject::connect(handle, &impl::ToplevelHandle::activatedChanged, this, &Toplevel::activatedChanged);
QObject::connect(handle, &wlr::ToplevelHandle::closed, this, &Toplevel::onClosed);
QObject::connect(handle, &wlr::ToplevelHandle::appIdChanged, this, &Toplevel::appIdChanged);
QObject::connect(handle, &wlr::ToplevelHandle::titleChanged, this, &Toplevel::titleChanged);
QObject::connect(handle, &wlr::ToplevelHandle::parentChanged, this, &Toplevel::parentChanged);
QObject::connect(handle, &wlr::ToplevelHandle::activatedChanged, this, &Toplevel::activatedChanged);
QObject::connect(&handle->visibleScreens, &WlOutputTracker::screenAdded, this, &Toplevel::screensChanged);
QObject::connect(&handle->visibleScreens, &WlOutputTracker::screenRemoved, this, &Toplevel::screensChanged);
QObject::connect(handle, &impl::ToplevelHandle::maximizedChanged, this, &Toplevel::maximizedChanged);
QObject::connect(handle, &impl::ToplevelHandle::minimizedChanged, this, &Toplevel::minimizedChanged);
QObject::connect(handle, &impl::ToplevelHandle::fullscreenChanged, this, &Toplevel::fullscreenChanged);
QObject::connect(handle, &wlr::ToplevelHandle::maximizedChanged, this, &Toplevel::maximizedChanged);
QObject::connect(handle, &wlr::ToplevelHandle::minimizedChanged, this, &Toplevel::minimizedChanged);
QObject::connect(handle, &wlr::ToplevelHandle::fullscreenChanged, this, &Toplevel::fullscreenChanged);
// clang-format on
}
@@ -99,7 +98,7 @@ void Toplevel::setRectangle(QObject* window, QRect rect) {
}
this->rectangle = rect;
this->handle->setRectangle(proxyWindow->backingWindow(), rect);
this->handle->setRectangle(proxyWindow ? proxyWindow->backingWindow() : nullptr, rect);
}
void Toplevel::unsetRectangle() { this->setRectangle(nullptr, QRect()); }
@@ -114,11 +113,11 @@ void Toplevel::onRectangleProxyDestroyed() {
}
ToplevelManager::ToplevelManager() {
auto* manager = impl::ToplevelManager::instance();
auto* manager = wlr::ToplevelManager::instance();
QObject::connect(
manager,
&impl::ToplevelManager::toplevelReady,
&wlr::ToplevelManager::toplevelReady,
this,
&ToplevelManager::onToplevelReady
);
@@ -128,7 +127,7 @@ ToplevelManager::ToplevelManager() {
}
}
Toplevel* ToplevelManager::forImpl(impl::ToplevelHandle* impl) const {
Toplevel* ToplevelManager::forImpl(wlr::ToplevelHandle* impl) const {
if (impl == nullptr) return nullptr;
for (auto* toplevel: this->mToplevels.valueList()) {
@@ -140,7 +139,7 @@ Toplevel* ToplevelManager::forImpl(impl::ToplevelHandle* impl) const {
ObjectModel<Toplevel>* ToplevelManager::toplevels() { return &this->mToplevels; }
void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
void ToplevelManager::onToplevelReady(wlr::ToplevelHandle* handle) {
auto* toplevel = new Toplevel(handle, this);
// clang-format off
@@ -191,4 +190,4 @@ Toplevel* ToplevelManagerQml::activeToplevel() {
return ToplevelManager::instance()->activeToplevel();
}
} // namespace qs::wayland::toplevel_management
} // namespace qs::wayland::toplevel
@@ -11,12 +11,12 @@
#include "../../core/util.hpp"
#include "../../window/proxywindow.hpp"
namespace qs::wayland::toplevel_management {
namespace qs::wayland::toplevel {
namespace impl {
namespace wlr {
class ToplevelManager; // NOLINT
class ToplevelHandle;
} // namespace impl
} // namespace wlr
///! Window from another application.
/// A window/toplevel from another application, retrievable from
@@ -26,7 +26,7 @@ class Toplevel: public QObject {
Q_PROPERTY(QString appId READ appId NOTIFY appIdChanged);
Q_PROPERTY(QString title READ title NOTIFY titleChanged);
/// Parent toplevel if this toplevel is a modal/dialog, otherwise null.
Q_PROPERTY(qs::wayland::toplevel_management::Toplevel* parent READ parent NOTIFY parentChanged);
Q_PROPERTY(qs::wayland::toplevel::Toplevel* parent READ parent NOTIFY parentChanged);
/// If the window is currently activated or focused.
///
/// Activation can be requested with the @@activate() function.
@@ -56,7 +56,7 @@ class Toplevel: public QObject {
QML_UNCREATABLE("Toplevels must be acquired from the ToplevelManager.");
public:
explicit Toplevel(impl::ToplevelHandle* handle, QObject* parent);
explicit Toplevel(wlr::ToplevelHandle* handle, QObject* parent);
/// Request that this toplevel is activated.
/// The request may be ignored by the compositor.
@@ -91,7 +91,7 @@ public:
[[nodiscard]] bool fullscreen() const;
void setFullscreen(bool fullscreen);
[[nodiscard]] impl::ToplevelHandle* implHandle() const { return this->handle; }
[[nodiscard]] wlr::ToplevelHandle* implHandle() const { return this->handle; }
signals:
void closed();
@@ -110,7 +110,7 @@ private slots:
void onRectangleProxyDestroyed();
private:
impl::ToplevelHandle* handle;
wlr::ToplevelHandle* handle;
ProxyWindowBase* rectWindow = nullptr;
QRect rectangle;
@@ -121,7 +121,7 @@ class ToplevelManager: public QObject {
Q_OBJECT;
public:
Toplevel* forImpl(impl::ToplevelHandle* impl) const;
Toplevel* forImpl(wlr::ToplevelHandle* impl) const;
[[nodiscard]] ObjectModel<Toplevel>* toplevels();
@@ -131,7 +131,7 @@ signals:
void activeToplevelChanged();
private slots:
void onToplevelReady(impl::ToplevelHandle* handle);
void onToplevelReady(wlr::ToplevelHandle* handle);
void onToplevelActiveChanged();
void onToplevelClosed();
@@ -158,13 +158,13 @@ class ToplevelManagerQml: public QObject {
Q_OBJECT;
// clang-format off
/// All toplevel windows exposed by the compositor.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::wayland::toplevel_management::Toplevel>*);
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::wayland::toplevel::Toplevel>*);
Q_PROPERTY(UntypedObjectModel* toplevels READ toplevels CONSTANT);
/// Active toplevel or null.
///
/// > [!INFO] If multiple are active, this will be the most recently activated one.
/// > Usually compositors will not report more than one toplevel as active at a time.
Q_PROPERTY(qs::wayland::toplevel_management::Toplevel* activeToplevel READ activeToplevel NOTIFY activeToplevelChanged);
Q_PROPERTY(qs::wayland::toplevel::Toplevel* activeToplevel READ activeToplevel NOTIFY activeToplevelChanged);
// clang-format on
QML_NAMED_ELEMENT(ToplevelManager);
QML_SINGLETON;
@@ -179,4 +179,4 @@ signals:
void activeToplevelChanged();
};
} // namespace qs::wayland::toplevel_management
} // namespace qs::wayland::toplevel
@@ -1,4 +1,4 @@
#include "handle.hpp"
#include "wlr_toplevel.hpp"
#include <cstddef>
#include <private/qwaylanddisplay_p.h>
@@ -13,13 +13,66 @@
#include <qobject.h>
#include <qscreen.h>
#include <qtmetamacros.h>
#include <qwaylandclientextension.h>
#include <wayland-util.h>
#include "manager.hpp"
#include "../../core/logcat.hpp"
#include "qwayland-wlr-foreign-toplevel-management-unstable-v1.h"
#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
namespace qs::wayland::toplevel_management::impl {
namespace qs::wayland::toplevel::wlr {
QS_LOGGING_CATEGORY(logToplevelManagement, "quickshell.wayland.toplevelManagement", QtWarningMsg);
ToplevelManager::ToplevelManager(): QWaylandClientExtensionTemplate(3) { this->initialize(); }
bool ToplevelManager::available() const { return this->isActive(); }
const QVector<ToplevelHandle*>& ToplevelManager::readyToplevels() const {
return this->mReadyToplevels;
}
ToplevelHandle* ToplevelManager::handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel) {
if (toplevel == nullptr) return nullptr;
for (auto* other: this->mToplevels) {
if (other->object() == toplevel) return other;
}
return nullptr;
}
ToplevelManager* ToplevelManager::instance() {
static auto* instance = new ToplevelManager(); // NOLINT
return instance;
}
void ToplevelManager::zwlr_foreign_toplevel_manager_v1_toplevel(
::zwlr_foreign_toplevel_handle_v1* toplevel
) {
auto* handle = new ToplevelHandle();
QObject::connect(handle, &ToplevelHandle::closed, this, &ToplevelManager::onToplevelClosed);
QObject::connect(handle, &ToplevelHandle::ready, this, &ToplevelManager::onToplevelReady);
qCDebug(logToplevelManagement) << "Toplevel handle created" << handle;
this->mToplevels.push_back(handle);
// Not done in constructor as a close could technically be picked up immediately on init,
// making touching the handle a UAF.
handle->init(toplevel);
}
void ToplevelManager::onToplevelReady() {
auto* handle = qobject_cast<ToplevelHandle*>(this->sender());
this->mReadyToplevels.push_back(handle);
emit this->toplevelReady(handle);
}
void ToplevelManager::onToplevelClosed() {
auto* handle = qobject_cast<ToplevelHandle*>(this->sender());
this->mReadyToplevels.removeOne(handle);
this->mToplevels.removeOne(handle);
}
QString ToplevelHandle::appId() const { return this->mAppId; }
QString ToplevelHandle::title() const { return this->mTitle; }
@@ -68,8 +121,11 @@ void ToplevelHandle::setRectangle(QWindow* window, QRect rect) {
}
}
QObject::disconnect(this->rectWindow, nullptr, this, nullptr);
this->rectWindow = nullptr;
if (this->rectWindow) {
QObject::disconnect(this->rectWindow, nullptr, this, nullptr);
this->rectWindow = nullptr;
}
return;
}
@@ -215,4 +271,4 @@ void ToplevelHandle::onParentClosed() {
emit this->parentChanged();
}
} // namespace qs::wayland::toplevel_management::impl
} // namespace qs::wayland::toplevel::wlr
@@ -1,15 +1,23 @@
#pragma once
#include <qloggingcategory.h>
#include <qobject.h>
#include <qrect.h>
#include <qscreen.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qvector.h>
#include <qwayland-wlr-foreign-toplevel-management-unstable-v1.h>
#include <qwaylandclientextension.h>
#include <qwindow.h>
#include "../../core/logcat.hpp"
#include "../output_tracking.hpp"
#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
namespace qs::wayland::toplevel_management::impl {
namespace qs::wayland::toplevel::wlr {
QS_DECLARE_LOGGING_CATEGORY(logToplevelManagement);
class ToplevelHandle
: public QObject
@@ -73,4 +81,34 @@ private:
QWindow* rectWindow = nullptr;
};
} // namespace qs::wayland::toplevel_management::impl
class ToplevelManager
: public QWaylandClientExtensionTemplate<ToplevelManager>
, public QtWayland::zwlr_foreign_toplevel_manager_v1 {
Q_OBJECT;
public:
[[nodiscard]] bool available() const;
[[nodiscard]] const QVector<ToplevelHandle*>& readyToplevels() const;
[[nodiscard]] ToplevelHandle* handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel);
static ToplevelManager* instance();
signals:
void toplevelReady(ToplevelHandle* toplevel);
protected:
explicit ToplevelManager();
void
zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel) override;
private slots:
void onToplevelReady();
void onToplevelClosed();
private:
QVector<ToplevelHandle*> mToplevels;
QVector<ToplevelHandle*> mReadyToplevels;
};
} // namespace qs::wayland::toplevel::wlr
@@ -1,68 +0,0 @@
#include "manager.hpp"
#include <qcontainerfwd.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qwaylandclientextension.h>
#include "../../core/logcat.hpp"
#include "handle.hpp"
#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
namespace qs::wayland::toplevel_management::impl {
QS_LOGGING_CATEGORY(logToplevelManagement, "quickshell.wayland.toplevelManagement", QtWarningMsg);
ToplevelManager::ToplevelManager(): QWaylandClientExtensionTemplate(3) { this->initialize(); }
bool ToplevelManager::available() const { return this->isActive(); }
const QVector<ToplevelHandle*>& ToplevelManager::readyToplevels() const {
return this->mReadyToplevels;
}
ToplevelHandle* ToplevelManager::handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel) {
if (toplevel == nullptr) return nullptr;
for (auto* other: this->mToplevels) {
if (other->object() == toplevel) return other;
}
return nullptr;
}
ToplevelManager* ToplevelManager::instance() {
static auto* instance = new ToplevelManager(); // NOLINT
return instance;
}
void ToplevelManager::zwlr_foreign_toplevel_manager_v1_toplevel(
::zwlr_foreign_toplevel_handle_v1* toplevel
) {
auto* handle = new ToplevelHandle();
QObject::connect(handle, &ToplevelHandle::closed, this, &ToplevelManager::onToplevelClosed);
QObject::connect(handle, &ToplevelHandle::ready, this, &ToplevelManager::onToplevelReady);
qCDebug(logToplevelManagement) << "Toplevel handle created" << handle;
this->mToplevels.push_back(handle);
// Not done in constructor as a close could technically be picked up immediately on init,
// making touching the handle a UAF.
handle->init(toplevel);
}
void ToplevelManager::onToplevelReady() {
auto* handle = qobject_cast<ToplevelHandle*>(this->sender());
this->mReadyToplevels.push_back(handle);
emit this->toplevelReady(handle);
}
void ToplevelManager::onToplevelClosed() {
auto* handle = qobject_cast<ToplevelHandle*>(this->sender());
this->mReadyToplevels.removeOne(handle);
this->mToplevels.removeOne(handle);
}
} // namespace qs::wayland::toplevel_management::impl
@@ -1,48 +0,0 @@
#pragma once
#include <qcontainerfwd.h>
#include <qloggingcategory.h>
#include <qtmetamacros.h>
#include <qwayland-wlr-foreign-toplevel-management-unstable-v1.h>
#include <qwaylandclientextension.h>
#include "../../core/logcat.hpp"
#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"
namespace qs::wayland::toplevel_management::impl {
class ToplevelHandle;
QS_DECLARE_LOGGING_CATEGORY(logToplevelManagement);
class ToplevelManager
: public QWaylandClientExtensionTemplate<ToplevelManager>
, public QtWayland::zwlr_foreign_toplevel_manager_v1 {
Q_OBJECT;
public:
[[nodiscard]] bool available() const;
[[nodiscard]] const QVector<ToplevelHandle*>& readyToplevels() const;
[[nodiscard]] ToplevelHandle* handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel);
static ToplevelManager* instance();
signals:
void toplevelReady(ToplevelHandle* toplevel);
protected:
explicit ToplevelManager();
void
zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel) override;
private slots:
void onToplevelReady();
void onToplevelClosed();
private:
QVector<ToplevelHandle*> mToplevels;
QVector<ToplevelHandle*> mReadyToplevels;
};
} // namespace qs::wayland::toplevel_management::impl
+4 -4
View File
@@ -50,7 +50,7 @@ class MarginWrapperManager: public WrapperManager {
Q_PROPERTY(qreal margin READ default WRITE default BINDABLE bindableMargin NOTIFY marginChanged FINAL);
/// An extra margin applied in addition to @@topMargin, @@bottomMargin,
/// @@leftMargin, and @@rightMargin. Defaults to 0.
Q_PROPERTY(qreal extraMargin READ default WRITE default BINDABLE bindableExtraMargin NOTIFY baseMarginChanged FINAL);
Q_PROPERTY(qreal extraMargin READ default WRITE default BINDABLE bindableExtraMargin NOTIFY extraMarginChanged FINAL);
/// The requested top margin of the content item, not counting @@extraMargin.
///
/// Defaults to @@margin, and may be reset by assigning `undefined`.
@@ -149,7 +149,7 @@ public:
signals:
void marginChanged();
void baseMarginChanged();
void extraMarginChanged();
void topMarginChanged();
void bottomMarginChanged();
void leftMarginChanged();
@@ -170,9 +170,9 @@ protected:
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MarginWrapperManager, bool, bResizeChild, true);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MarginWrapperManager, bool, bResizeChild, true, &MarginWrapperManager::resizeChildChanged);
Q_OBJECT_BINDABLE_PROPERTY(MarginWrapperManager, qreal, bMargin, &MarginWrapperManager::marginChanged);
Q_OBJECT_BINDABLE_PROPERTY(MarginWrapperManager, qreal, bExtraMargin, &MarginWrapperManager::baseMarginChanged);
Q_OBJECT_BINDABLE_PROPERTY(MarginWrapperManager, qreal, bExtraMargin, &MarginWrapperManager::extraMarginChanged);
Q_OBJECT_BINDABLE_PROPERTY(MarginWrapperManager, OverrideFlags, bOverrides);
Q_OBJECT_BINDABLE_PROPERTY(MarginWrapperManager, qreal, bImplicitWidthOverride);
+6 -1
View File
@@ -144,5 +144,10 @@ private:
&ProxyPopupWindow::targetVisibleChanged
);
Q_OBJECT_BINDABLE_PROPERTY(ProxyPopupWindow, bool, bWantsGrab);
Q_OBJECT_BINDABLE_PROPERTY(
ProxyPopupWindow,
bool,
bWantsGrab,
&ProxyPopupWindow::grabFocusChanged
);
};
+14 -12
View File
@@ -495,6 +495,10 @@ void ProxyWindowBase::setUpdatesEnabled(bool updatesEnabled) {
if (this->window != nullptr) {
QQuickWindowPrivate::get(this->window)->updatesEnabled = updatesEnabled;
// The render loop discards expose and update requests while updates are disabled,
// which can leave the surface without a valid buffer. Render a frame to recover.
if (updatesEnabled) this->window->update();
}
emit this->updatesEnabledChanged();
@@ -612,9 +616,11 @@ void ProxyWindowAttached::setWindow(ProxyWindowBase* window) {
emit this->windowChanged();
}
ProxiedWindow::ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent)
: QQuickWindow(parent)
, mProxy(proxy) {
namespace {
QList<std::function<void(QQuickWindow*)>> SCENEGRAPH_INIT_CALLBACKS; // NOLINT
}
QsQuickWindowBase::QsQuickWindowBase(QWindow* parent): QQuickWindow(parent) {
QObject::connect(
this,
&QQuickWindow::sceneGraphInitialized,
@@ -623,15 +629,7 @@ ProxiedWindow::ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent)
);
}
namespace {
QList<std::function<void(QQuickWindow*)>> SCENEGRAPH_INIT_CALLBACKS; // NOLINT
}
void ProxiedWindow::callOnScenegraphInit(std::function<void(QQuickWindow*)> cb) { // NOLINT
SCENEGRAPH_INIT_CALLBACKS.emplaceBack(cb);
}
void ProxiedWindow::onSceneGraphInitialized() {
void QsQuickWindowBase::onSceneGraphInitialized() {
for (auto& cb: SCENEGRAPH_INIT_CALLBACKS) {
cb(this);
}
@@ -639,6 +637,10 @@ void ProxiedWindow::onSceneGraphInitialized() {
SCENEGRAPH_INIT_CALLBACKS.clear();
}
void QsQuickWindowBase::callOnScenegraphInit(std::function<void(QQuickWindow*)> cb) { // NOLINT
SCENEGRAPH_INIT_CALLBACKS.emplaceBack(cb);
}
bool ProxiedWindow::event(QEvent* event) {
if (event->type() == QEvent::DevicePixelRatioChange) {
emit this->devicePixelRatioChanged();
+16 -7
View File
@@ -252,24 +252,33 @@ private:
void setWindow(ProxyWindowBase* window);
};
class ProxiedWindow: public QQuickWindow {
class QsQuickWindowBase: public QQuickWindow {
Q_OBJECT;
public:
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr);
explicit QsQuickWindowBase(QWindow* parent = nullptr);
static void callOnScenegraphInit(std::function<void(QQuickWindow*)> cb);
private slots:
void onSceneGraphInitialized();
};
class ProxiedWindow: public QsQuickWindowBase {
Q_OBJECT;
public:
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr)
: QsQuickWindowBase(parent)
, mProxy(proxy) {}
[[nodiscard]] ProxyWindowBase* proxy() const { return this->mProxy; }
void setProxy(ProxyWindowBase* proxy) { this->mProxy = proxy; }
static void callOnScenegraphInit(std::function<void(QQuickWindow*)> cb);
signals:
void exposed();
void devicePixelRatioChanged();
private slots:
void onSceneGraphInitialized();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
+3
View File
@@ -148,6 +148,9 @@ class WindowInterface: public Reloadable {
/// When set to false, the window will not re-render in response to animations
/// or other visual updates from other windows. This is useful for static windows
/// such as wallpapers that do not need to update frequently, saving GPU cycles.
///
/// When set back to true, a new frame is rendered, including any changes made
/// while updates were disabled.
Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged);
Q_PROPERTY(QQmlListProperty<QObject> data READ data);
// clang-format on
+12 -6
View File
@@ -30,16 +30,22 @@ WindowsetProjection::WindowsetProjection(QObject* parent): QObject(parent) {
}
return result;
});
}
this->bScreens.setBinding([this] {
QList<QuickshellScreenInfo*> screens;
QList<QuickshellScreenInfo*> WindowsetProjection::screens() {
QList<QuickshellScreenInfo*> screens;
for (auto* screen: this->bQScreens.value()) {
screens.append(QuickshellTracked::instance()->screenInfo(screen));
for (auto* screen: this->bQScreens.value()) {
if (auto* qsScreen = QuickshellTracked::instance()->screenInfo(screen)) {
screens.append(qsScreen);
} else {
qCDebug(logWorkspace) << this << "screens list contains invalid screen"
<< static_cast<void*>(screen)
<< "which was probably removed before workspace state has settled.";
}
}
return screens;
});
return screens;
}
} // namespace qs::wm
+4 -8
View File
@@ -132,7 +132,7 @@ class WindowsetProjection: public QObject {
QML_UNCREATABLE("");
// clang-format off
/// Screens the windowset projection spans, often a single screen or all screens.
Q_PROPERTY(QList<QuickshellScreenInfo*> screens READ default NOTIFY screensChanged BINDABLE bindableScreens);
Q_PROPERTY(QList<QuickshellScreenInfo*> screens READ screens NOTIFY screensChanged);
/// Windowsets that are currently present on the projection.
Q_PROPERTY(QList<Windowset*> windowsets READ default NOTIFY windowsetsChanged BINDABLE bindableWindowsets);
// clang-format on
@@ -140,9 +140,7 @@ class WindowsetProjection: public QObject {
public:
explicit WindowsetProjection(QObject* parent);
[[nodiscard]] QBindable<QList<QuickshellScreenInfo*>> bindableScreens() const {
return &this->bScreens;
}
[[nodiscard]] QList<QuickshellScreenInfo*> screens();
[[nodiscard]] QBindable<QList<QScreen*>> bindableQScreens() const { return &this->bQScreens; }
@@ -155,12 +153,10 @@ signals:
void windowsetsChanged();
protected:
Q_OBJECT_BINDABLE_PROPERTY(WindowsetProjection, QList<QScreen*>, bQScreens);
Q_OBJECT_BINDABLE_PROPERTY(
WindowsetProjection,
QList<QuickshellScreenInfo*>,
bScreens,
QList<QScreen*>,
bQScreens,
&WindowsetProjection::screensChanged
);
+1 -1
View File
@@ -57,7 +57,7 @@ public:
}
// from the bottom up, update all panels
for (auto* panel: panels) {
for (auto* panel: this->mPanels[panel->engineGeneration]) {
panel->updateDimensions();
}
}