53 Commits
Author SHA1 Message Date
outfoxxed 4f508be500 version: 0.3.2 2026-10-08 01:42:54 -07:00
outfoxxed 11ca60be22 docs: add PR template and clarify AI usage in pr descriptions 2026-10-05 21:20:58 -07:00
Carson Powers 8fd0e25844 networking: prefer AP security 2026-10-05 11:03:58 -07:00
Cloud0310 5d5d49873f all: use MOC includes instead of opaque pointer defiinitions
Fixes linking issues and Qt 6.12 compatibility.
2026-10-03 04:43:26 -07:00
ayandexyz 41651d7dcd io/socket: discard sockets which have failed to connect
Fixes future connection attempts.
2026-09-25 06:40:25 -07:00
bbedward 4594680eb4 core/generation: destroy the engine after the root's children 2026-09-25 00:15:27 -07:00
faithleysath 25da30db02 pipewire: write route volumes without volume step 2026-09-25 00:11:17 -07:00
outfoxxed fae96f1a5b hyprland/ipc: support workspace addresses 2026-09-20 22:04:49 -07:00
outfoxxed c6a516096d core/scriptmodel: avoid comparing native types as equal by fields 2026-09-15 01:37:42 -07:00
ZANX3Y 86b4275879 services/notifications: fix action text updates 2026-09-13 18:59:02 -07:00
qaqland 9b4b206961 x11/i3: build the module without requiring X11
BUILD.md documents i3/Sway as having no x11 dependency, but the module was
only added from src/x11/CMakeLists.txt, so -DI3=ON -DX11=OFF silently lost
Quickshell.I3.
2026-09-13 18:59:02 -07:00
outfoxxed 92cb8909eb all: ensure deleteLater'd QObjects are disconnected 2026-09-13 18:59:00 -07:00
outfoxxed 2d3b3e9c70 core/generation: destroy generation extensions after js interruption 2026-08-29 02:11:43 -07:00
outfoxxed ec0be5b36f launch: initialize QuickshellTracked before almost anything else
This fixes consumers like WlSessionLock getting connections to
QGuiApplication::screenAdded before QuickshellTracked::updateScreens
can run if the user never references the Quickshell singleton from QML
first.
2026-08-28 19:27:13 -07:00
outfoxxed fd116897cf launch: create a QCoreApplication for non launch command paths
Things that need an event loop break without this.
2026-08-28 19:21:49 -07:00
outfoxxed 916a0dd90c launch: avoid creating multiple QApplications
Accessibility hooks created by QQuick module init are destroyed by the
QApplication destructor and not recreated by a new QApplication.
2026-08-28 00:48:19 -07:00
Carson Powers 0f9939ca4f networking: add NetworkManager service watcher 2026-08-25 23:36:53 -07:00
Carson Powers 052059fb7c networking: handle omitted mode setting in NM 2026-08-25 23:14:48 -07:00
outfoxxed 39e14d3627 service/tray: support /NO_DBUSMENU as a marker for absence of a menu
I couldn't find any documentation for this, but it was requested and
KDE also does it.
2026-08-25 23:03:30 -07:00
outfoxxed 9f59d0a05d wayland/lock: add warning on live session lock destruction 2026-08-25 17:22:27 -07:00
outfoxxed afb2c27cd6 wayland/lock: guard against reentrancy during surface creation 2026-08-25 02:14:21 -07:00
outfoxxed 0fed22a2c4 service/pam: fix free of wrong pointer in pam subprocess messaging 2026-08-22 16:08:41 -07:00
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
101 changed files with 1381 additions and 1006 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
+31
View File
@@ -0,0 +1,31 @@
<!--
Read https://github.com/quickshell-mirror/quickshell/blob/master/CONTRIBUTING.md before opening a PR.
Failing to do so may result in a ban from opening PRs.
-->
### Describe your changes
<!-- High level overview of what you are changing and why. AI may not be used to write this. -->
### Details and descisions
<!--
Put details of why you took a given approach to problems here if its not obvious.
Reviewing PRs is easier if we don't have to reverse engineer your reasoning.
Consider if things you put here should be code comments instead.
If an AI was heavily used to make the change and its context might be useful,
put it in a spoiler under your own reasoning.
-->
### Open concerns
<!-- Anything you're not sure of or want help with? -->
### Participation
<!--
With the advent of AI, we get a lot more drive-bys that don't care about participating
in a traditional review process, and just want to get a change in.
If you care about your code or learning how the project works to contribute in the future,
please say so. If not, we'll assume you don't particularly care.
-->
+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.2" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)
+3
View File
@@ -9,6 +9,9 @@ To ensure nobody's time is wasted, please follow the rules below.
your change works, do not submit it to be merged. You must be able to explain your reasoning
for every change.
- Change descriptions MUST NOT be written by AI. Summarize what you changed in your own words.
If you think an AI summary of a chat session etc may provide useful context, include it in a spoiler.
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
responsible for such contribution attempts **will be banned**.
+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.
+17
View File
@@ -0,0 +1,17 @@
## Other Changes
- Added support for Qt 6.12
- Added support for Hyprland's new workspace address format.
## Bug Fixes
- Fixed main process crashes on pam subprocess misbehavior.
- Fixed crashes when attempting to create or modify session locks reentrantly.
- Fixed networking state breaking after restarting NetworkManager.
- Fixed accessibility information not being provided for Quickshell windows.
- Fixed `WlSessionLockSurface.screen` being null and never updating with a real screen on monitor plug.
- Fixed `NotificationAction.text` not accepting text updates.
- Fixed PipeWire route volume writes for devices without a reported volume step.
- Fixed a crash when quitting or reloading while a `Loader` was still loading asynchronously.
- Fixed future `Socket` connection attempts after a failed connection attempt.
- Fixed Open wifi networks showing as Unknown security.
+1 -1
View File
@@ -56,7 +56,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.3.0";
version = "0.3.2";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
+4
View File
@@ -29,6 +29,10 @@ if (X11)
add_subdirectory(x11)
endif()
if (I3)
add_subdirectory(x11/i3)
endif()
add_subdirectory(services)
if (BLUETOOTH)
+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 {}
+32 -19
View File
@@ -11,6 +11,7 @@
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
@@ -62,6 +63,10 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
EngineGeneration::~EngineGeneration() {
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->engine != nullptr) {
qFatal() << this << "destroyed without calling destroy()";
}
@@ -78,30 +83,37 @@ void EngineGeneration::destroy() {
this->watcher = nullptr;
}
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->root != nullptr) {
// prevent further js execution during teardown of the root's children, gc and the engine.
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
// prevent further js execution between garbage collection and engine destruction.
this->engine->setInterrupted(true);
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
});
// QObject::destroyed is emitted before the root's children are destroyed.
// The engine must outlive them, as objects such as Loaders with in-flight
// incubations access it in their destructors.
QObject::connect(
this->root,
&QObject::destroyed,
this,
[this]() {
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
},
Qt::QueuedConnection
);
this->root->deleteLater();
this->root = nullptr;
} else {
@@ -198,6 +210,7 @@ void EngineGeneration::setWatchingFiles(bool watching) {
}
} else {
if (this->watcher != nullptr) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+21 -19
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;
@@ -331,28 +335,33 @@ void LogManager::init(
qInstallMessageHandler(&LogManager::messageHandler);
qCDebug(logLogging) << "Creating offthread logger...";
auto* thread = new QThread();
instance->threadProxy.moveToThread(thread);
thread->start();
QMetaObject::invokeMethod(
&instance->threadProxy,
&LoggingThreadProxy::initInThread,
Qt::BlockingQueuedConnection
);
instance->threadLogging = new ThreadLogging(instance);
instance->threadLogging->init();
qCDebug(logLogging) << "Logger initialized.";
}
void LogManager::initThreadLogging() {
auto* instance = LogManager::instance();
qCDebug(logLogging) << "Moving logger to dedicated thread...";
auto* thread = new QThread();
instance->threadLogging->setParent(nullptr);
instance->threadLogging->moveToThread(thread);
thread->start();
qCDebug(logLogging) << "Logger thread initialized.";
}
void initLogCategoryLevel(const char* name, QtMsgType defaultLevel) {
LogManager::instance()->defaultLevels.insert(QLatin1StringView(name), defaultLevel);
}
void LogManager::initFs() {
QMetaObject::invokeMethod(
&LogManager::instance()->threadProxy,
"initFs",
LogManager::instance()->threadLogging,
&ThreadLogging::initFs,
Qt::BlockingQueuedConnection
);
}
@@ -365,13 +374,6 @@ CategoryFilter LogManager::getFilter(QLatin1StringView category) {
return this->allFilters.value(category);
}
void LoggingThreadProxy::initInThread() {
this->logging = new ThreadLogging(this);
this->logging->init();
}
void LoggingThreadProxy::initFs() { this->logging->initFs(); }
void ThreadLogging::init() {
auto logMfd = memfd_create("quickshell:logs", 0);
+2 -15
View File
@@ -55,20 +55,6 @@ size_t qHash(const LogMessage& message);
class ThreadLogging;
class LoggingThreadProxy: public QObject {
Q_OBJECT;
public:
explicit LoggingThreadProxy() = default;
public slots:
void initInThread();
void initFs();
private:
ThreadLogging* logging = nullptr;
};
namespace qt_logging_registry {
class QLoggingRule;
}
@@ -104,6 +90,7 @@ public:
const QString& prefix = ""
);
static void initThreadLogging();
static void initFs();
static LogManager* instance();
@@ -137,7 +124,7 @@ private:
QTextStream stdoutStream;
QMutex stdoutMutex;
LoggingThreadProxy threadProxy;
ThreadLogging* threadLogging = nullptr;
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
};
+3 -1
View File
@@ -114,9 +114,11 @@ public:
explicit ThreadLogging(QObject* parent): QObject(parent) {}
void init();
void initFs();
void setupFileLogging();
public slots:
void initFs();
private slots:
void onMessage(const LogMessage& msg, bool showInSparse);
+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);
+14 -6
View File
@@ -88,6 +88,8 @@ QuickshellTracked::QuickshellTracked() {
}
}
// FIXME: Some callers can potentially pass a freed QScreen here which is why we don't init a new one.
// The QuickshellTracked::init() in launch.cpp makes sure our screenAdded handler runs first for now.
QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
for (auto* info: this->screens) {
if (info->screen == screen) return info;
@@ -96,13 +98,19 @@ QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
return nullptr;
}
namespace {
static QuickshellTracked* qsTrackedInstance = nullptr; // NOLINT
}
void QuickshellTracked::init() {
if (qsTrackedInstance) qFatal() << "Tried to reinitialize QuickshellTracked";
qsTrackedInstance = new QuickshellTracked();
QJSEngine::setObjectOwnership(qsTrackedInstance, QJSEngine::CppOwnership);
}
QuickshellTracked* QuickshellTracked::instance() {
static QuickshellTracked* instance = nullptr; // NOLINT
if (instance == nullptr) {
QJSEngine::setObjectOwnership(instance, QJSEngine::CppOwnership);
instance = new QuickshellTracked();
}
return instance;
if (!qsTrackedInstance) qFatal() << "Tried to get QuickshellTracked instance before init";
return qsTrackedInstance;
}
void QuickshellTracked::updateScreens() {
+1
View File
@@ -70,6 +70,7 @@ public:
QVector<QuickshellScreenInfo*> screens;
QuickshellScreenInfo* screenInfo(QScreen* screen) const;
static void init();
static QuickshellTracked* instance();
private slots:
+7 -1
View File
@@ -74,6 +74,7 @@ void QsMenuAnchor::onClosed() {
this->mOpen = false;
if (this->platformMenu) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
@@ -101,7 +102,12 @@ void QsMenuAnchor::setMenu(QsMenuHandle* menu) {
if (menu == this->mMenu) return;
if (this->mMenu != nullptr) {
if (this->platformMenu != nullptr) this->platformMenu->deleteLater();
if (this->platformMenu != nullptr) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
}
+118 -28
View File
@@ -4,39 +4,102 @@
#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;
const auto jsValueType = QMetaType::fromType<QJSValue>();
const auto aIsNative = a.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
const auto bIsNative = b.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
if (aIsNative || bIsNative) return false;
if (!a.isObject() || !b.isObject() || a.isCallable() || b.isCallable()) return false;
if (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 +112,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 +123,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 +146,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 +154,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 +171,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
iter = this->mValues.begin() + (index + len);
this->endMoveRows();
anyChanges = true;
}
} else {
auto startNewIter = newIter;
@@ -130,8 +197,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 +208,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 +253,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 +266,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(
+2
View File
@@ -272,6 +272,7 @@ void Process::onStarted() {
}
void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) {
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
if (this->mStdoutParser) this->mStdoutParser->streamEnded(this->stdoutBuffer);
@@ -290,6 +291,7 @@ void Process::onErrorOccurred(QProcess::ProcessError error) {
if (error == QProcess::FailedToStart) { // other cases should be covered by other events
qWarning() << "Process failed to start, likely because the binary could not be found. Command:"
<< this->mCommand;
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
emit this->runningChanged();
+14 -1
View File
@@ -17,7 +17,11 @@
QS_LOGGING_CATEGORY(logSocket, "quickshell.io.socket", QtWarningMsg);
void Socket::setSocket(QLocalSocket* socket) {
if (this->socket != nullptr) this->socket->deleteLater();
if (this->socket != nullptr) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
}
this->socket = socket;
if (socket != nullptr) {
@@ -57,6 +61,7 @@ void Socket::onSocketDisconnected() {
qCDebug(logSocket) << "Socket disconnected:" << this;
this->connected = false;
this->disconnecting = false;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->buffer.clear();
@@ -68,6 +73,13 @@ void Socket::onSocketDisconnected() {
void Socket::onSocketError(QLocalSocket::LocalSocketError error) {
qCWarning(logSocket) << "Socket error for" << this << error;
emit this->error(error);
if (!this->connected) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->disconnecting = false;
}
}
bool Socket::isConnected() const { return this->connected; }
@@ -191,6 +203,7 @@ void SocketServer::disableServer() {
}
this->mSockets.clear();
QObject::disconnect(this->server, nullptr, this, nullptr);
this->server->close();
this->server->deleteLater();
this->server = nullptr;
+12 -3
View File
@@ -62,6 +62,7 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->deleteLater();
}
@@ -105,11 +106,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
+67 -49
View File
@@ -2,6 +2,7 @@
#include <array>
#include <cerrno>
#include <cstring>
#include <functional>
#include <utility>
#include <qconfig.h>
@@ -164,7 +165,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 +179,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 +202,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 +269,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;
}
@@ -453,7 +466,7 @@ int ipcCommand(CommandState& cmd) {
});
}
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
int launchFromCommand(CommandState& cmd) {
QString configPath;
auto r = locateConfigFile(cmd, configPath);
@@ -461,7 +474,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;
}
@@ -473,14 +486,13 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
.debugPort = cmd.debug.port,
.waitForDebug = cmd.debug.wait,
},
cmd.exec.argv,
coreApplication
cmd.exec.argv
);
}
} // namespace
int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
int runCommand(int argc, char** argv) {
auto state = CommandState();
if (auto ret = parseCommand(argc, argv, state); ret != 65535) return ret;
@@ -543,20 +555,24 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
);
}
std::function<int(CommandState&)> continuation;
if (state.misc.printVersion) {
if (state.log.verbosity == 0) {
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
} else {
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
}
return 0;
} else if (*state.subcommand.log) {
return readLogFile(state);
continuation = readLogFile;
} else if (*state.subcommand.list) {
return listInstances(state);
continuation = listInstances;
} else if (*state.subcommand.kill) {
return killInstances(state);
continuation = killInstances;
} else if (*state.subcommand.msg || *state.ipc.ipc) {
return ipcCommand(state);
continuation = ipcCommand;
} else {
if (strcmp(qVersion(), QT_VERSION_STR) != 0) {
qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR
@@ -565,10 +581,12 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
"the quickshell package must be rebuilt.\n";
}
return launchFromCommand(state, coreApplication);
return launchFromCommand(state);
}
return 0;
int qArgc = 1;
auto coreApp = QCoreApplication(qArgc, argv);
return continuation(state);
}
QString getDisplayConnection() {
+9 -4
View File
@@ -22,6 +22,7 @@
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "../core/plugin.hpp"
#include "../core/qmlglobal.hpp"
#include "../core/rootwrapper.hpp"
#include "../ipc/ipc.hpp"
#include "build.hpp"
@@ -57,7 +58,7 @@ QString base36Encode(T number) {
} // namespace
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) {
int launch(const LaunchArgs& args, char** argv) {
auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex();
auto shellId = QString(pathId);
@@ -171,7 +172,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
QsPaths::instance()->linkRunDir();
QsPaths::instance()->linkPathDir();
LogManager::initFs();
Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment();
@@ -279,8 +279,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware);
delete coreApplication;
QGuiApplication* app = nullptr;
auto qArgC = 0;
@@ -290,6 +288,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
app = new QGuiApplication(qArgC, argv);
}
LogManager::initThreadLogging();
LogManager::initFs();
QGuiApplication::setDesktopFileName(appId);
if (args.debugPort != -1) {
@@ -299,6 +300,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait);
}
// This needs to run early to get the first connection to QGuiApplication::screenAdded() in Qs.
// If we don't do that, attempts to get a QuickshellScreenInfo from a QScreen will fail in screenAdded bindings.
QuickshellTracked::init();
QsEnginePlugin::initPlugins();
// Base window transparency appears to be additive.
+2 -2
View File
@@ -107,10 +107,10 @@ struct LaunchArgs {
void exitDaemon(int code);
int parseCommand(int argc, char** argv, CommandState& state);
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
int runCommand(int argc, char** argv);
QString getDisplayConnection();
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
int launch(const LaunchArgs& args, char** argv);
} // namespace qs::launch
+21 -17
View File
@@ -24,23 +24,31 @@ namespace qs::launch {
namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
void checkCrashRelaunch(char** argv) {
#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"
@@ -64,7 +71,7 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
} else {
qCritical() << "Quickshell has been restarted.";
launch({.configPath = info.instance.configPath}, argv, coreApplication);
launch({.configPath = info.instance.configPath}, argv);
}
}
#endif
@@ -116,11 +123,8 @@ int main(int argc, char** argv) {
qsCheckCrash(argc, argv);
#endif
auto qArgC = 1;
auto* coreApplication = new QCoreApplication(qArgC, argv);
checkCrashRelaunch(argv, coreApplication);
auto code = runCommand(argc, argv, coreApplication);
checkCrashRelaunch(argv);
auto code = runCommand(argc, argv);
exitDaemon(code);
return code;
+40 -12
View File
@@ -6,6 +6,7 @@
#include <qdbusmetatype.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qdbusservicewatcher.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@@ -42,6 +43,17 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
return;
}
// clang-format off
QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceRegistered, this, &NetworkManager::onServiceRegistered);
QObject::connect(&this->serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &NetworkManager::onServiceUnregistered);
// clang-format on
this->serviceWatcher.setWatchMode(
QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration
);
this->serviceWatcher.addWatchedService("org.freedesktop.NetworkManager");
this->serviceWatcher.setConnection(bus);
this->proxy = new DBusNetworkManagerProxy(
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager",
@@ -49,25 +61,20 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
this
);
if (!this->proxy->isValid()) {
qCDebug(
logNetworkManager
) << "NetworkManager is not currently running. This network backend will not work";
} else {
this->init();
}
}
void NetworkManager::init() {
// clang-format off
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded);
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved);
// clang-format on
this->dbusProperties.setInterface(this->proxy);
this->dbusProperties.updateAllViaGetAll();
this->registerDevices();
if (!this->proxy->isValid()) {
qCDebug(
logNetworkManager
) << "NetworkManager is not currently running. This network backend will not work";
} else {
this->onServiceRegistered();
}
}
void NetworkManager::checkConnectivity() {
@@ -230,6 +237,27 @@ void NetworkManager::setWifiEnabled(bool enabled) {
this->pWifiEnabled.write();
}
void NetworkManager::onServiceRegistered() {
qCDebug(logNetworkManager) << "NetworkManager service registered";
this->dbusProperties.updateAllViaGetAll();
this->registerDevices();
}
void NetworkManager::onServiceUnregistered() {
qCDebug(logNetworkManager) << "NetworkManager service unregistered";
// Clear all remaining devices
const auto devices = this->mDevices;
this->mDevices.clear();
for (auto* device: devices) {
if (!device) continue;
qCDebug(logNetworkManager) << "Device removed:" << device->path();
emit this->deviceRemoved(device->frontend());
delete device;
}
}
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
} // namespace qs::network
+4 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qdbusextratypes.h>
#include <qdbusservicewatcher.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
@@ -59,6 +60,8 @@ public slots:
private slots:
void onDevicePathAdded(const QDBusObjectPath& path);
void onDevicePathRemoved(const QDBusObjectPath& path);
void onServiceRegistered();
void onServiceUnregistered();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
@@ -67,7 +70,6 @@ private slots:
);
private:
void init();
void registerDevices();
void registerDevice(const QString& path);
@@ -88,6 +90,7 @@ private:
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled");
// clang-format on
DBusNetworkManagerProxy* proxy = nullptr;
QDBusServiceWatcher serviceWatcher;
};
} // namespace qs::network
+7 -5
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);
@@ -185,10 +186,10 @@ NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device,
, bSecurity(WifiSecurityType::Unknown) {
auto updateSecurity = [this]() {
if (NMSettings* settings = this->bReferenceSettings) {
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
} else if (NMAccessPoint* ap = this->bReferenceAp) {
if (NMAccessPoint* ap = this->bReferenceAp) {
this->bSecurity.setBinding([ap]() { return ap->security(); });
} else if (NMSettings* settings = this->bReferenceSettings) {
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
} else {
this->bSecurity = WifiSecurityType::Unknown;
}
@@ -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
+1 -1
View File
@@ -20,7 +20,7 @@ namespace qs::network {
WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings) {
const QString mapName = "802-11-wireless-security";
if (!settings.contains(mapName)) return WifiSecurityType::Unknown;
if (!settings.contains(mapName)) return WifiSecurityType::Open;
const QVariantMap& security = settings.value(mapName);
if (security.isEmpty()) return WifiSecurityType::Open;
+2 -1
View File
@@ -124,7 +124,8 @@ void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
const auto ssid = map["802-11-wireless"]["ssid"].toString();
const auto mode = map["802-11-wireless"]["mode"].toString();
if (mode == "infrastructure") {
// Omitted mode is assumed to be "infrastructure".
if (mode.isEmpty() || mode == "infrastructure") {
auto* net = this->mNetworks.value(ssid);
if (!net) net = this->registerNetwork(ssid);
net->addSettings(settings);
+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; }
+3 -1
View File
@@ -65,8 +65,10 @@ void MprisWatcher::onServiceRegistered(const QString& service) {
void MprisWatcher::onServiceUnregistered(const QString& service) {
if (auto* player = this->mPlayers.value(service)) {
player->deleteLater();
QObject::disconnect(player, nullptr, this, nullptr);
this->mPlayers.remove(service);
this->readyPlayers.removeObject(player);
player->deleteLater();
qCDebug(logMprisWatcher) << "Unregistered MprisPlayer" << service;
} else {
qCWarning(logMprisWatcher) << "Got service unregister event for untracked service" << service;
+1 -1
View File
@@ -56,7 +56,7 @@ void NotificationAction::invoke() {
}
void NotificationAction::setText(const QString& text) {
if (text != this->mText) return;
if (text == this->mText) return;
this->mText = text;
emit this->textChanged();
+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
+1 -1
View File
@@ -201,6 +201,6 @@ int PamSubprocess::conversation(
return PAM_SUCCESS;
fail:
free(responseArray); // NOLINT
free(responses); // NOLINT
_exit(1);
}
+1
View File
@@ -102,6 +102,7 @@ void PwConnection::startSocketWatcher() {
void PwConnection::stopSocketWatcher() {
if (this->socketWatcher == nullptr) return;
QObject::disconnect(this->socketWatcher, nullptr, this, nullptr);
this->socketWatcher->deleteLater();
this->socketWatcher = nullptr;
}
+18 -18
View File
@@ -486,8 +486,8 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
<< "via device";
this->waitingVolumes = realVolumes;
} else {
auto significantChange = this->mServerVolumes.isEmpty() || this->volumeStep == -1;
if (this->volumeStep != -1) {
auto significantChange = this->mServerVolumes.isEmpty();
for (auto i = 0; i < this->mServerVolumes.length(); i++) {
auto serverVolume = this->mServerVolumes.value(i);
auto targetVolume = realVolumes.value(i);
@@ -496,25 +496,25 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
break;
}
}
}
if (significantChange) {
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes
<< "via device";
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
return;
}
this->mDeviceVolumes = realVolumes;
this->node->device->waitForDevice();
} else {
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
// "waiting for acknowledgement" state forever.
qCInfo(logNode).nospace()
<< "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
<< this->mServerVolumes
<< " as it is a device node and the change is too small (min step: "
<< this->volumeStep << ").";
if (significantChange) {
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes
<< "via device";
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
return;
}
this->mDeviceVolumes = realVolumes;
this->node->device->waitForDevice();
} else {
// Insignificant changes won't cause an info event on the device, leaving qs hung in the
// "waiting for acknowledgement" state forever.
qCInfo(logNode).nospace()
<< "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
<< this->mServerVolumes
<< " as it is a device node and the change is too small (min step: " << this->volumeStep
<< ").";
}
}
} else {
+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 -7
View File
@@ -8,18 +8,12 @@
#include <qvector.h>
#include "node.hpp"
#include "qml.hpp"
namespace qs::service::pipewire {
class PwNodeIface;
class PwPeakStream;
} // namespace qs::service::pipewire
Q_DECLARE_OPAQUE_POINTER(qs::service::pipewire::PwNodeIface*);
namespace qs::service::pipewire {
///! Monitors peak levels of an audio node.
/// Tracks volume peaks for a node across all its channels.
///
+4 -2
View File
@@ -43,6 +43,8 @@ void PolkitAgentImpl::cancelAllRequests(const QString& reason) {
auto* flow = this->bActiveFlow.value();
if (flow) {
QObject::disconnect(flow, nullptr, this, nullptr);
this->bActiveFlow = nullptr;
flow->cancelAuthenticationRequest();
flow->deleteLater();
}
@@ -169,12 +171,12 @@ void PolkitAgentImpl::finishAuthenticationRequest() {
qCDebug(logPolkit) << "finishing authentication request for action"
<< this->bActiveFlow.value()->actionId();
QObject::disconnect(this->bActiveFlow.value(), nullptr, this, nullptr);
this->bActiveFlow.value()->deleteLater();
this->bActiveFlow = nullptr;
if (!this->queuedRequests.empty()) {
this->activateAuthenticationRequest();
} else {
this->bActiveFlow = nullptr;
}
}
} // namespace qs::service::polkit
+10 -3
View File
@@ -103,7 +103,12 @@ StatusNotifierItem::StatusNotifierItem(const QString& address, QObject* parent)
return this->imageHandle.url() % "/" % QString::number(this->pixmapIndex);
});
this->bHasMenu.setBinding([this]() { return !this->bMenuPath.value().path().isEmpty(); });
this->bHasMenu.setBinding([this]() {
// Could not find any documentation about No_DBUSMENU but KDE checks for it
// and it was requested.
return !this->bMenuPath.value().path().isEmpty()
&& this->bMenuPath.value().path() != "/NO_DBUSMENU";
});
QObject::connect(
this->item,
@@ -270,11 +275,13 @@ void StatusNotifierItem::scroll(qint32 delta, bool horizontal) const {
void StatusNotifierItem::updatePixmapIndex() { this->pixmapIndex = this->pixmapIndex + 1; }
DBusMenuHandle* StatusNotifierItem::menuHandle() {
return this->bMenuPath.value().path().isEmpty() ? nullptr : &this->mMenuHandle;
return !this->bHasMenu ? nullptr : &this->mMenuHandle;
}
void StatusNotifierItem::onMenuPathChanged() {
this->mMenuHandle.setAddress(this->item->service(), this->bMenuPath.value().path());
auto path = this->bMenuPath.value().path();
if (!this->bHasMenu) path = "";
this->mMenuHandle.setAddress(this->item->service(), path);
}
void StatusNotifierItem::onGetAllFinished() {
+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;
}
+58 -40
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"
@@ -37,6 +37,18 @@ QS_LOGGING_CATEGORY(logHyprlandIpc, "quickshell.hyprland.ipc", QtWarningMsg);
QS_LOGGING_CATEGORY(logHyprlandIpcEvents, "quickshell.hyprland.ipc.events", QtWarningMsg);
} // namespace
QBindable<HyprlandMonitor*> HyprlandIpc::bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
QBindable<HyprlandWorkspace*> HyprlandIpc::bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
QBindable<HyprlandToplevel*> HyprlandIpc::bindableActiveToplevel() const {
return &this->bActiveToplevel;
}
HyprlandIpc::HyprlandIpc() {
auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) {
@@ -139,11 +151,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;
@@ -188,11 +199,13 @@ void HyprlandIpc::makeRequest(
requestSocket->flush();
};
auto errorCallback = [=](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
requestSocket->deleteLater();
callback(false, {});
};
auto errorCallback =
[this, requestSocket, request, callback](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
QObject::disconnect(requestSocket, nullptr, this, nullptr);
requestSocket->deleteLater();
callback(false, {});
};
QObject::connect(requestSocket, &QLocalSocket::connected, this, connectedCallback);
QObject::connect(requestSocket, &QLocalSocket::errorOccurred, this, errorCallback);
@@ -306,7 +319,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
const auto& mList = this->mMonitors.valueList();
auto name = QString::fromUtf8(event->data);
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -331,10 +344,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "createworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
qCDebug(logHyprlandIpc) << "Workspace created with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace created with address" << address << "name" << name;
auto* workspace = this->findWorkspaceByName(name, false);
auto existed = workspace != nullptr;
@@ -343,7 +356,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace = new HyprlandWorkspace(this);
}
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
if (!existed) {
this->refreshWorkspaces(false);
@@ -352,17 +365,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "destroyworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) {
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << address << "name" << name
<< "which was not previously tracked.";
return;
}
@@ -370,7 +383,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
auto index = workspaceIter - mList.begin();
auto* workspace = *workspaceIter;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << address << "name" << name;
this->mWorkspaces.removeAt(index);
// workspaces have not been observed to be referenced after deletion
@@ -399,10 +412,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
this->setFocusedMonitor(monitor);
monitor->setActiveWorkspace(workspace);
qCDebug(logHyprlandIpc) << "Monitor" << name << "focused with workspace"
<< (workspace ? workspace->bindableId().value() : -1);
<< (workspace ? workspace->bindableAddress().value() : "<unk>");
} else if (event->name == "workspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
if (this->bFocusedMonitor != nullptr) {
@@ -413,7 +426,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
} else if (event->name == "moveworkspacev2") {
auto args = event->parseView(3);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
auto monitorName = QString::fromUtf8(args.at(2));
@@ -424,18 +437,18 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace->setMonitor(monitor);
} else if (event->name == "renameworkspace") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) return;
qCDebug(logHyprlandIpc) << "Workspace with id" << id << "renamed from"
qCDebug(logHyprlandIpc) << "Workspace with address" << address << "renamed from"
<< (*workspaceIter)->bindableName().value() << "to" << name;
(*workspaceIter)->bindableName().setValue(name);
@@ -487,7 +500,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
if (!ok) return;
const auto& mList = this->mToplevels.valueList();
auto toplevelIter = std::ranges::find_if(mList, [windowAddress](HyprlandToplevel* m) {
auto toplevelIter = std::ranges::find_if(mList, [&](HyprlandToplevel* m) {
return m->address() == windowAddress;
});
@@ -579,14 +592,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
}
HyprlandWorkspace*
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
HyprlandWorkspace* HyprlandIpc::findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address
) {
const auto& mList = this->mWorkspaces.valueList();
HyprlandWorkspace* workspace = nullptr;
if (id != -1) {
if (!address.isEmpty()) {
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
@@ -604,10 +620,11 @@ HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint
return workspace;
} else if (createIfMissing) {
qCDebug(logHyprlandIpc) << "Workspace" << name
<< "requested before creation, performing early init with id" << id;
<< "requested before creation, performing early init with address"
<< address;
auto* workspace = new HyprlandWorkspace(this);
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
return workspace;
} else {
@@ -627,15 +644,16 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto json = QJsonDocument::fromJson(resp).array();
const auto& mList = this->mWorkspaces.valueList();
auto ids = QVector<quint32>();
auto addresses = QVector<QString>();
for (auto entry: json) {
auto object = entry.toObject().toVariantMap();
auto id = object.value("id").toInt();
auto address = object.value("address").toString();
if (address.isEmpty()) address = QString::number(object.value("id").toInt());
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
// Only fall back to name-based filtering as a last resort, for workspaces where
@@ -644,7 +662,7 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto name = object.value("name").toString();
workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == -1 && m->bindableName().value() == name;
return m->bindableAddress().value().isEmpty() && m->bindableName().value() == name;
});
}
@@ -662,14 +680,14 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
}
ids.push_back(id);
addresses.push_back(address);
}
if (canCreate) {
auto removedWorkspaces = QVector<HyprlandWorkspace*>();
for (auto* workspace: mList) {
if (!ids.contains(workspace->bindableId().value())) {
if (!addresses.contains(workspace->bindableAddress().value())) {
removedWorkspaces.push_back(workspace);
}
}
@@ -751,7 +769,7 @@ HyprlandMonitor*
HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
const auto& mList = this->mMonitors.valueList();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -819,7 +837,7 @@ void HyprlandIpc::refreshMonitors(bool canCreate) {
auto object = entry.toObject().toVariantMap();
auto name = object.value("name").toString();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
+13 -20
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 {
@@ -25,9 +25,9 @@ class HyprlandToplevel;
} // namespace qs::hyprland::ipc
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandWorkspace*);
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandMonitor*);
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandToplevel*);
Q_MOC_INCLUDE("hyprland_toplevel.hpp")
Q_MOC_INCLUDE("monitor.hpp")
Q_MOC_INCLUDE("workspace.hpp")
namespace qs::hyprland::ipc {
@@ -83,17 +83,9 @@ public:
[[nodiscard]] QBindable<bool> bindableUsingLua() const { return &this->bUsingLua; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
[[nodiscard]] QBindable<HyprlandToplevel*> bindableActiveToplevel() const {
return &this->bActiveToplevel;
}
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const;
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableFocusedWorkspace() const;
[[nodiscard]] QBindable<HyprlandToplevel*> bindableActiveToplevel() const;
void setFocusedMonitor(HyprlandMonitor* monitor);
@@ -102,7 +94,11 @@ public:
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels();
// No byId because these preemptively create objects. The given id is set if created.
HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandWorkspace* findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address = QString()
);
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandToplevel* findToplevelByAddress(quint64 address, bool createIfMissing);
@@ -128,10 +124,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,15 +6,20 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../toplevel_management/qml.hpp"
#include "../../toplevel/qml.hpp"
#include "connection.hpp"
#include "monitor.hpp"
#include "toplevel_mapping.hpp"
#include "workspace.hpp"
using namespace qs::wayland::toplevel_management;
using namespace qs::wayland::toplevel;
namespace qs::hyprland::ipc {
QBindable<HyprlandWorkspace*> HyprlandToplevel::bindableWorkspace() { return &this->bWorkspace; }
QBindable<HyprlandMonitor*> HyprlandToplevel::bindableMonitor() { return &this->bMonitor; }
HyprlandToplevel::HyprlandToplevel(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
this->bMonitor.setBinding([this]() {
return this->bWorkspace ? this->bWorkspace->bindableMonitor().value() : nullptr;
@@ -112,7 +117,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; }
@@ -81,10 +81,10 @@ public:
return &this->bLastIpcObject;
};
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableWorkspace() { return &this->bWorkspace; }
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableWorkspace();
void setWorkspace(HyprlandWorkspace* workspace);
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor();
signals:
void addressChanged();
@@ -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
+8 -1
View File
@@ -13,6 +13,10 @@ namespace qs::hyprland::ipc {
QVariantMap HyprlandMonitor::lastIpcObject() const { return this->mLastIpcObject; }
QBindable<HyprlandWorkspace*> HyprlandMonitor::bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QString& description) {
Qt::beginPropertyUpdateGroup();
this->bId = id;
@@ -28,7 +32,9 @@ void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QStrin
void HyprlandMonitor::updateFromObject(QVariantMap object) {
auto activeWorkspaceObj = object.value("activeWorkspace").value<QVariantMap>();
auto activeWorkspaceAddress = activeWorkspaceObj.value("address").value<QString>();
auto activeWorkspaceId = activeWorkspaceObj.value("id").value<qint32>();
if (activeWorkspaceAddress.isEmpty()) activeWorkspaceAddress = QString::number(activeWorkspaceId);
auto activeWorkspaceName = activeWorkspaceObj.value("name").value<QString>();
auto focused = object.value("focused").value<bool>();
@@ -46,7 +52,8 @@ void HyprlandMonitor::updateFromObject(QVariantMap object) {
if (this->bActiveWorkspace == nullptr
|| this->bActiveWorkspace->bindableName().value() != activeWorkspaceName)
{
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId);
auto* workspace =
this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceAddress);
workspace->setMonitor(this);
this->setActiveWorkspace(workspace);
}
+1 -3
View File
@@ -54,9 +54,7 @@ public:
[[nodiscard]] QBindable<qint32> bindableHeight() { return &this->bHeight; }
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableActiveWorkspace() const;
[[nodiscard]] QBindable<bool> bindableFocused() const { return &this->bFocused; }
@@ -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;
};
+19 -7
View File
@@ -15,14 +15,23 @@ namespace qs::hyprland::ipc {
QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObject; }
QBindable<HyprlandMonitor*> HyprlandWorkspace::bindableMonitor() { return &this->bMonitor; }
HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
this->bActive.setBinding([this]() {
this->bId.setBinding([this] {
auto idOk = false;
auto id = this->bAddress.value().toInt(&idOk);
if (!idOk) id = -1;
return id;
});
this->bActive.setBinding([this] {
return this->bMonitor.value() && this->bMonitor->bindableActiveWorkspace().value() == this;
});
this->bFocused.setBinding([this]() {
this->bFocused.setBinding([this] {
return this->ipc->bindableFocusedWorkspace().value() == this;
});
@@ -35,9 +44,9 @@ HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::endPropertyUpdateGroup();
}
void HyprlandWorkspace::updateInitial(qint32 id, const QString& name) {
void HyprlandWorkspace::updateInitial(const QString& address, const QString& name) {
Qt::beginPropertyUpdateGroup();
this->bId = id;
this->bAddress = address;
this->bName = name;
Qt::endPropertyUpdateGroup();
}
@@ -47,11 +56,14 @@ void HyprlandWorkspace::updateFromObject(QVariantMap object) {
auto monitorName = object.value("monitor").value<QString>();
auto hasFullscreen = object.value("hasfullscreen").value<bool>();
auto initial = this->bId == -1;
auto initial = this->bAddress.value().isEmpty();
// ID cannot be updated after creation
// Address cannot be updated after creation
if (initial) {
this->bId = object.value("id").value<qint32>();
auto address = object.value("address").value<QString>();
auto id = object.value("id").value<qint32>();
if (address.isEmpty()) address = QString::number(id);
this->bAddress = address;
}
// No events we currently handle give a workspace id but not a name,
+14 -2
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qbytearrayview.h>
#include <qcontainerfwd.h>
#include <qjsonobject.h>
#include <qobject.h>
#include <qproperty.h>
@@ -18,6 +19,13 @@ class HyprlandMonitor;
class HyprlandWorkspace: public QObject {
Q_OBJECT;
// clang-format off
// > [!NOTE] On Hyprland versions prior to 0.57, this property is set to @@id as a string for compatibility.
// >
// > Prefer this property over @@id for new code.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
// > [!NOTE] On Hyprland versions 0.57 and later, this property is set to @@address as an integer for compatibility.
// >
// > After 0.57, this property will equal `-1` for non-numbered workspaces.
Q_PROPERTY(qint32 id READ default NOTIFY idChanged BINDABLE bindableId);
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// If this workspace is currently active on its monitor. See also @@focused.
@@ -47,7 +55,7 @@ class HyprlandWorkspace: public QObject {
public:
explicit HyprlandWorkspace(HyprlandIpc* ipc);
void updateInitial(qint32 id, const QString& name);
void updateInitial(const QString& address, const QString& name);
void updateFromObject(QVariantMap object);
/// Activate the workspace.
@@ -58,13 +66,14 @@ public:
/// > ```
Q_INVOKABLE void activate();
[[nodiscard]] QBindable<QString> bindableAddress() { return &this->bAddress; }
[[nodiscard]] QBindable<qint32> bindableId() { return &this->bId; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] QBindable<bool> bindableHasFullscreen() { return &this->bHasFullscreen; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor();
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels() { return &this->mToplevels; }
[[nodiscard]] QVariantMap lastIpcObject() const;
@@ -75,7 +84,9 @@ public:
void removeToplevel(HyprlandToplevel* toplevel);
signals:
void addressChanged();
void idChanged();
void typeChanged();
void nameChanged();
void activeChanged();
void focusedChanged();
@@ -97,6 +108,7 @@ private:
ObjectModel<HyprlandToplevel> mToplevels {this};
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bAddress, &HyprlandWorkspace::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(HyprlandWorkspace, qint32, bId, -1, &HyprlandWorkspace::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bName, &HyprlandWorkspace::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bActive, &HyprlandWorkspace::activeChanged);
@@ -16,7 +16,7 @@ FloatingWindow {
color: "lightgray"
ColumnLayout {
Text { text: `Workspace ${wsDelegate.modelData.id} on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
Text { text: `Workspace (addr ${wsDelegate.modelData.address}; id ${wsDelegate.modelData.id}; name: ${wsDelegate.modelData.name}) on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
ColumnLayout {
Repeater {
+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);
+75 -44
View File
@@ -19,8 +19,18 @@
#include "../core/qmlglobal.hpp"
#include "../core/qmlscreen.hpp"
#include "../core/reload.hpp"
#include "../window/proxywindow.hpp"
#include "session_lock/session_lock.hpp"
WlSessionLock::~WlSessionLock() {
if (this->isLocked()) {
qCritical() << "Session lock object was destroyed without unlocking. The session will stay "
"locked in case this was accidental.";
qCritical() << "Component.onDestruction may be used if this behavior is required, but it may "
"result in a lock failing open.";
}
}
void WlSessionLock::onReload(QObject* oldInstance) {
auto* old = qobject_cast<WlSessionLock*>(oldInstance);
@@ -55,9 +65,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;
}
@@ -109,70 +122,84 @@ void WlSessionLock::updateSurfaces(bool show, WlSessionLock* old) {
}
void WlSessionLock::realizeLockTarget(WlSessionLock* old) {
if (this->lockTarget) {
if (!SessionLockManager::lockAvailable()) {
qCritical() << "Cannot start session lock: The current compositor does not support the "
"ext-session-lock-v1 protocol.";
this->unlock();
return;
if (this->realizing || !this->manager) {
this->awaitingRealization = true;
return;
}
this->realizing = true;
[&, this]() {
if (this->lockTarget) {
if (!SessionLockManager::lockAvailable()) {
qCritical() << "Cannot start session lock: The current compositor does not support the "
"ext-session-lock-v1 protocol.";
this->unlock();
return;
}
if (this->mSurfaceComponent == nullptr) {
qWarning() << "WlSessionLock.surface is null. Aborting lock.";
this->unlock();
return;
}
// preload initial surfaces to make the chance of the compositor displaying a blank
// frame before the lock surfaces are shown as low as possible.
this->updateSurfaces(false);
auto wasLocked = this->isLocked();
if (!this->manager->lock()) {
qWarning() << "Failed to acquire session lock.";
this->unlock();
return;
}
this->updateSurfaces(true, old);
if (!wasLocked) emit this->lockStateChanged();
} else {
this->unlock(); // emits lockStateChanged
}
}();
if (this->mSurfaceComponent == nullptr) {
qWarning() << "WlSessionLock.surface is null. Aborting lock.";
this->unlock();
return;
}
this->realizing = false;
// preload initial surfaces to make the chance of the compositor displaying a blank
// frame before the lock surfaces are shown as low as possible.
this->updateSurfaces(false);
if (!this->manager->lock()) this->lockTarget = false;
this->updateSurfaces(true, old);
} else {
this->unlock(); // emits lockStateChanged
if (this->awaitingRealization) {
this->awaitingRealization = false;
this->realizeLockTarget();
}
}
void WlSessionLock::unlock() {
if (this->isLocked()) {
this->lockTarget = false;
this->manager->unlock();
this->lockTarget = false;
for (auto* surface: this->surfaces) {
surface->deleteLater();
}
this->surfaces.clear();
emit this->lockStateChanged();
for (auto* surface: this->surfaces) {
surface->deleteLater();
}
this->surfaces.clear();
if (this->manager) this->manager->unlock();
if (this->isLocked()) emit this->lockStateChanged();
}
void WlSessionLock::onScreensChanged() {
if (this->manager != nullptr && this->manager->isLocked()) {
// Theoretically screens shouldnt be invalidated while realizing, instead in later event loop cycles.
if (this->isLocked() && !this->realizing) {
this->updateSurfaces(true);
}
}
bool WlSessionLock::isLocked() const {
return this->manager == nullptr ? this->lockTarget : this->manager->isLocked();
}
bool WlSessionLock::isLocked() const { return this->manager && this->manager->isLocked(); }
bool WlSessionLock::isSecure() const {
return this->manager != nullptr && SessionLockManager::isSecure();
}
void WlSessionLock::setLocked(bool locked) {
if (this->isLocked() == locked) return;
if (this->lockTarget == locked) return;
this->lockTarget = locked;
if (this->manager == nullptr) {
emit this->lockStateChanged();
return;
}
this->realizeLockTarget();
}
@@ -206,6 +233,7 @@ WlSessionLockSurface::WlSessionLockSurface(QObject* parent)
WlSessionLockSurface::~WlSessionLockSurface() {
if (this->window != nullptr) {
this->window->destroy();
this->window->deleteLater();
}
}
@@ -216,7 +244,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 +301,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;
+4
View File
@@ -73,6 +73,8 @@ class WlSessionLock: public Reloadable {
public:
explicit WlSessionLock(QObject* parent = nullptr): Reloadable(parent) {}
~WlSessionLock() override;
Q_DISABLE_COPY_MOVE(WlSessionLock);
void onReload(QObject* oldInstance) override;
@@ -101,6 +103,8 @@ private:
QQmlComponent* mSurfaceComponent = nullptr;
QMap<QScreen*, WlSessionLockSurface*> surfaces;
bool lockTarget = false;
bool realizing = false;
bool awaitingRealization = false;
friend class WlSessionLockSurface;
};
+2 -1
View File
@@ -25,7 +25,8 @@ QSWaylandSessionLockManager* manager() {
bool SessionLockManager::lockAvailable() { return manager()->isActive(); }
bool SessionLockManager::lock() {
if (this->isLocked() || SessionLockManager::sessionLocked()) return false;
if (this->isLocked()) return true;
if (SessionLockManager::sessionLocked()) return false;
this->mLock = manager()->acquireLock();
this->mLock->setParent(this);
+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
);
-4
View File
@@ -11,10 +11,6 @@ qt_add_qml_module(quickshell-x11
DEPENDENCIES QtQuick
)
if(I3)
add_subdirectory(i3)
endif()
install_qml_module(quickshell-x11)
add_library(quickshell-x11-init OBJECT init.cpp)
+8
View File
@@ -31,6 +31,14 @@ QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg);
QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg);
} // namespace
QBindable<I3Monitor*> I3IpcController::bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
QBindable<I3Workspace*> I3IpcController::bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
}
I3IpcController::I3IpcController(): I3Ipc({"workspace", "output"}) {
// bind focused workspace to focused monitor's active workspace
this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* {
+4 -9
View File
@@ -20,8 +20,8 @@ class I3Workspace;
class I3Monitor;
} // namespace qs::i3::ipc
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*);
Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*);
Q_MOC_INCLUDE("monitor.hpp")
Q_MOC_INCLUDE("workspace.hpp")
namespace qs::i3::ipc {
@@ -43,13 +43,8 @@ public:
I3Monitor* monitorFor(QuickshellScreenInfo* screen);
[[nodiscard]] QBindable<I3Monitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
};
[[nodiscard]] QBindable<I3Workspace*> bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
};
[[nodiscard]] QBindable<I3Monitor*> bindableFocusedMonitor() const;
[[nodiscard]] QBindable<I3Workspace*> bindableFocusedWorkspace() const;
[[nodiscard]] ObjectModel<I3Monitor>* monitors();
[[nodiscard]] ObjectModel<I3Workspace>* workspaces();
+4
View File
@@ -12,6 +12,10 @@
namespace qs::i3::ipc {
QBindable<I3Workspace*> I3Monitor::bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
I3Monitor::I3Monitor(I3IpcController* ipc): QObject(ipc), ipc(ipc) {
// clang-format off
this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; });
+1 -3
View File
@@ -55,9 +55,7 @@ public:
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() const;
[[nodiscard]] QVariantMap lastIpcObject() const;
+2
View File
@@ -12,6 +12,8 @@
namespace qs::i3::ipc {
QBindable<I3Monitor*> I3Workspace::bindableMonitor() const { return &this->bMonitor; }
I3Workspace::I3Workspace(I3IpcController* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
+1 -1
View File
@@ -57,7 +57,7 @@ public:
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() const { return &this->bMonitor; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() const;
[[nodiscard]] QVariantMap lastIpcObject() const;
void updateFromObject(const QVariantMap& obj);
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more