mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-28 02:55:05 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f508be500 | ||
|
|
11ca60be22 | ||
|
|
8fd0e25844 | ||
|
|
5d5d49873f | ||
|
|
41651d7dcd | ||
|
|
4594680eb4 | ||
|
|
25da30db02 | ||
|
|
fae96f1a5b | ||
|
|
c6a516096d | ||
|
|
86b4275879 | ||
|
|
9b4b206961 | ||
|
|
92cb8909eb | ||
|
|
2d3b3e9c70 | ||
|
|
ec0be5b36f | ||
|
|
fd116897cf | ||
|
|
916a0dd90c | ||
|
|
0f9939ca4f | ||
|
|
052059fb7c | ||
|
|
39e14d3627 | ||
|
|
9f59d0a05d | ||
|
|
afb2c27cd6 | ||
|
|
0fed22a2c4 | ||
|
|
1a4716cde7 | ||
|
|
069e33a93e | ||
|
|
532a0606ca | ||
|
|
78672a8405 | ||
|
|
990a8badab | ||
|
|
7e6c7ae3fb | ||
|
|
8f1316d0c2 | ||
|
|
63eb2ba6ef | ||
|
|
b1fc3e0740 | ||
|
|
21fec77de7 | ||
|
|
d9cbc90a8c | ||
|
|
28771c7c74 | ||
|
|
43d4fa9e88 | ||
|
|
10b439fc6e | ||
|
|
e649d24749 | ||
|
|
025c709e94 | ||
|
|
4df562dfb2 | ||
|
|
98b6d0b7e7 | ||
|
|
25eb04fb06 | ||
|
|
ead3b00afd | ||
|
|
a611932352 | ||
|
|
1661ef100a | ||
|
|
68c2c85c33 | ||
|
|
897fcdaa99 | ||
|
|
d99d87d5e5 | ||
|
|
b66495fcc5 | ||
|
|
eaba6c8b62 | ||
|
|
4b4fca3224 | ||
|
|
7d1c9a9c67 | ||
|
|
d1760ed1f3 | ||
|
|
b93d61385f |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
-->
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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**.
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -29,6 +29,10 @@ if (X11)
|
||||
add_subdirectory(x11)
|
||||
endif()
|
||||
|
||||
if (I3)
|
||||
add_subdirectory(x11/i3)
|
||||
endif()
|
||||
|
||||
add_subdirectory(services)
|
||||
|
||||
if (BLUETOOTH)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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() {
|
||||
|
||||
@@ -70,6 +70,7 @@ public:
|
||||
QVector<QuickshellScreenInfo*> screens;
|
||||
QuickshellScreenInfo* screenInfo(QScreen* screen) const;
|
||||
|
||||
static void init();
|
||||
static QuickshellTracked* instance();
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -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
@@ -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"}}; }
|
||||
@@ -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
@@ -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);
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -201,6 +201,6 @@ int PamSubprocess::conversation(
|
||||
return PAM_SUCCESS;
|
||||
|
||||
fail:
|
||||
free(responseArray); // NOLINT
|
||||
free(responses); // NOLINT
|
||||
_exit(1);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
-2
@@ -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
|
||||
File renamed without changes.
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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* {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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; });
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
Reference in new issue
Block a user