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

These accesses could be observed by not setting `values` to a binding,
access before updateValuesUnique finished, or randomly before a
binding had been fully updated.
2026-07-06 23:45:42 -07:00
outfoxxed 1661ef100a wm: recompute WindowsetProjection.screens on each usage
Fixes potential UAF from destroyed screens not yet removed by a
wayland commit.
2026-06-30 02:31:19 -07:00
bbedward 68c2c85c33 core/window: render a frame when re-enabling updatesEnabled 2026-06-18 22:43:11 -07:00
luke 897fcdaa99 wayland/lock: avoid creating lock surfaces for uninited wl screens 2026-06-17 22:59:12 -07:00
outfoxxed d99d87d5e5 crash: remove logs from crash template 2026-06-09 18:04:48 -07:00
outfoxxed b66495fcc5 all: add/fix property change signals on various properties
- FileView.atomicWrites notified on blockWrites
- StatusNotifierItem.onlyMenu missing notification
- MarginWrapper.extraMargin missing notification
- MarginWrapper.resizeChild missing notification
- PopupWindow.grabFocus missing notification
2026-06-01 01:48:57 -07:00
outfoxxed eaba6c8b62 services/mpris: fix minRate and maxRate returning rate 2026-06-01 01:37:19 -07:00
outfoxxed 4b4fca3224 docs: document private ABI usage 2026-05-21 23:14:12 -07:00
outfoxxed 7d1c9a9c67 wayland/toplevel: reorganize toplevel management 2026-05-11 01:38:16 -07:00
Aditya Alok d1760ed1f3 wayland/lock: add window null check in surface isVisible 2026-05-07 23:40:27 -07:00
outfoxxed b93d61385f wayland/lock: run scenegraph callbacks for lock surfaces
Fixes ScreencopyView never initializing if only using lock surfaces.
2026-05-07 03:08:57 -07:00
outfoxxed 59e9c47b0e version: 0.3.0 2026-05-03 22:09:49 -07:00
outfoxxed ffe95f06ac guix: drop
Hard to keep the package up to date without an active maintainer.
2026-05-03 18:50:15 -07:00
outfoxxed 8db8ca1fec core/log: mask virtual/override property warnings 2026-05-02 00:15:59 -07:00
outfoxxed 0baa81aa03 all: fix typos 2026-04-27 18:56:41 -07:00
outfoxxed 01834e9c8f hyprland/ipc: add lua ipc support 2026-04-27 18:56:39 -07:00
outfoxxed 2ba2ae48b4 core: add qt version check in preprocessor 2026-04-27 02:18:32 -07:00
Jörg Thalheim 055e384668 core/model: make diffUpdate handle reordering without duplicating rows
diffUpdate inserted an element at its target index without removing it
from its previous index, so a pure permutation like [d,a,b,c] ->
[a,b,c,d] left every misplaced element in the model twice and the
removal pass never reclaimed them because their pointer is still in
newValues. Any caller that re-sorts an existing list (dbusmenu
re-layout, desktopentry re-scan) can hit this. Found via a downstream
niri workspace backend where a monitor hot-plug re-sorts the model.
2026-04-26 12:59:02 +02:00
outfoxxed 11a71d233a dbus/dbusmenu: connect onItemPropertiesUpdated
Evidently this was broken at some point or never connected.
2026-04-24 21:01:49 -07:00
outfoxxed e162429b6f wayland/toplevel: track global screen removals
Screen removal events aren't necessarily emitted at a protocol level
when a screen is removed globally.

This would have caused UAFs if the pointer was ever dereferenced, but
in practice it was only compared against.
2026-04-24 01:30:59 -07:00
outfoxxed 783c953987 services/pipewire: assume default channels in channelMap if unset
Pw-pulse seems to have stopped sending these at some point. This
copies the logic that pipewire's tools use to decide the default channels.
2026-04-22 03:34:08 -07:00
outfoxxed b850b8a1a9 core: add DefaultEnv pragma 2026-04-22 01:12:10 -07:00
Carson Powers d60498adc0 networking: add wired device support 2026-04-21 01:48:20 -07:00
outfoxxed 9a54119893 core/command: warn on mismatch when other displays have instances 2026-04-19 02:01:41 -07:00
outfoxxed 18075d1218 core/command: ignore QT_QPA_PLATFORM in display check 2026-04-19 02:01:19 -07:00
134 changed files with 2611 additions and 1781 deletions

No files matched your search

+3 -9
View File
@@ -13,17 +13,11 @@ body:
id: report
attributes:
label: Report file
description: Attach `report.txt` here.
validations:
required: true
- type: upload
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it.
Attach `report.txt` here.
You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
Please ensure the report does not contain personal information,
as reports may contain detailed logs leading up to the crash.
validations:
required: true
- type: textarea
+3 -7
View File
@@ -39,6 +39,9 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `pkg-config` (build-time)
- `cli11` (static library)
Quickshell relies on private Qt APIs and **MUST** be rebuilt against the each Qt release
or crashes via ABI mismatches will occur.
Build time dependencies and static libraries don't have to exist at runtime,
however build time dependencies must be compiled for the architecture of
the builder, while static libraries must be compiled for the architecture
@@ -97,13 +100,6 @@ To disable: `-DSOCKETS=OFF`
### Wayland
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
at runtime.
Currently supported Qt versions: `6.6`, `6.7`.
To disable: `-DWAYLAND=OFF`
Dependencies:
+2 -6
View File
@@ -1,11 +1,7 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
project(quickshell VERSION "0.3.1" LANGUAGES CXX C)
set(UNRELEASED_FEATURES
"network.2"
"colorquant-imagerect"
"window-parent"
)
set(UNRELEASED_FEATURES)
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
+5 -1
View File
@@ -31,10 +31,10 @@ set shell id.
- Added per-corner radius support to Region.
- Added ColorQuantizer region selection.
- Added dialog window support to FloatingWindow.
- Added lua config support to Hyprland module.
## Other Changes
- FreeBSD is now partially supported.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
@@ -44,6 +44,7 @@ set shell id.
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
- Added `DefaultEnv` pragma which sets an environment variable if not already set.
- Unrecognized pragmas are no longer a hard error for future backward compatibility.
## Bug Fixes
@@ -75,6 +76,9 @@ set shell id.
- Fixed pipewire crashes after hotplugging devices and changing default outputs.
- Fixed launches failing for `--daemonize` on some systems.
- Fixed screencopy crashing when used across GPUs.
- Fixed pipewire volumes not working for some pw-pulse clients.
- Fixed nulls in Toplevel.screens after unplugging a monitor.
- Fixed some DbusMenu updates being dropped from apps.
## Packaging Changes
+23
View File
@@ -0,0 +1,23 @@
## Bug Fixes
- Fixed `ScreencopyView` not displaying when only lock surfaces are shown.
- Fixed `WlSessionLockSurface.visible` crashing if accessed before backing surface creation.
- Fixed `MprisPlayer` returning `rate` for `minRate` and `maxRate`.
- Fixed missing/wrong change signals on various properties.
- Fixed session lock crashes on sleep, wake, DPMS, and unlocking.
- `QsWindow.updatesEnabled` makes sure windows are redrawn when set to true.
- Fixed potential crashes from usage of `WindowsetProjection.screens` during monitor unplug.
- Fixed crashes from accessing freed objects laundered through a `ScriptModel`.
- Fixed crashes when a wifi network disappear.
- Fixed unhandled notifications sending `NotificationClosed` out of order.
- Fixed `qs kill` not waiting for the process to exit.
- Fixed IPC calls from children of a crashed and relaunched process crashing.
- Fixed `JsonAdapter` crashing when deserializing new objects into an array.
- Fixed `Toplevel.unsetRectangle` crashing when calle.
- Fixed intermittent `FileView` crashes when updating watched files.
- Fixed `ColorQuantizer` deletion crashing if an operation was live.
- Fixed crashes when failing to create a `ScreencopyView`.
- Fixed `PwNodePeakMonitor` crashing when sampling a stream with mismatched channels from the node.
- Fixed unsetting `PopupAnchor.item` causing a crash.
- Fixed hiding the last `PanelWindow` on screen causing a crash under X11.
- Fixed crashes when `ScriptModel.values` is set while processing previously set values.
+1 -1
View File
@@ -56,7 +56,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.2.1";
version = "0.3.1";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
-77
View File
@@ -1,77 +0,0 @@
(define-module (quickshell)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (gnu packages cpp)
#:use-module (gnu packages freedesktop)
#:use-module (gnu packages gcc)
#:use-module (gnu packages gl)
#:use-module (gnu packages jemalloc)
#:use-module (gnu packages linux)
#:use-module (gnu packages ninja)
#:use-module (gnu packages pkg-config)
#:use-module (gnu packages qt)
#:use-module (gnu packages vulkan)
#:use-module (gnu packages xdisorg)
#:use-module (gnu packages xorg)
#:use-module (guix build-system cmake)
#:use-module (guix download)
#:use-module (guix gexp)
#:use-module (guix git-download)
#:use-module (guix packages)
#:use-module (guix packages)
#:use-module (guix utils))
(define-public quickshell-git
(package
(name "quickshell")
(version "git")
(source (local-file "." "quickshell-checkout"
#:recursive? #t
#:select? (or (git-predicate (current-source-directory))
(const #t))))
(build-system cmake-build-system)
(propagated-inputs (list qtbase qtdeclarative qtsvg))
(native-inputs (list ninja
gcc-14
pkg-config
qtshadertools
spirv-tools
wayland-protocols
cli11))
(inputs (list jemalloc
libdrm
libxcb
libxkbcommon
linux-pam
polkit
mesa
pipewire
qtbase
qtdeclarative
qtwayland
vulkan-headers
wayland))
(arguments
(list #:tests? #f
#:configure-flags
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
"-DCRASH_HANDLER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
(replace 'install (lambda _ (invoke "cmake" "--install" ".")))
(add-after 'install 'wrap-program
(lambda* (#:key inputs #:allow-other-keys)
(wrap-program (string-append #$output "/bin/quickshell")
`("QML_IMPORT_PATH" ":"
= (,(getenv "QML_IMPORT_PATH")))))))))
(home-page "https://quickshell.outfoxxed.me")
(synopsis "QtQuick-based desktop shell toolkit")
(description
"Quickshell is a flexible QtQuick-based toolkit for creating and
customizing toolbars, notification centers, and other desktop
environment tools in a live programming environment.")
(license license:lgpl3)))
quickshell-git
+9 -6
View File
@@ -1,5 +1,6 @@
#include "colorquantizer.hpp"
#include <algorithm>
#include <utility>
#include <qatomic.h>
#include <qcolor.h>
@@ -26,12 +27,12 @@ QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg
}
ColorQuantizerOperation::ColorQuantizerOperation(
QUrl* source,
QUrl source,
qreal depth,
QRect imageRect,
qreal rescaleSize
)
: source(source)
: source(std::move(source))
, maxDepth(depth)
, imageRect(imageRect)
, rescaleSize(rescaleSize) {
@@ -39,11 +40,11 @@ ColorQuantizerOperation::ColorQuantizerOperation(
}
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
if (shouldCancel.loadAcquire() || this->source.isEmpty()) return;
this->colors.clear();
auto image = QImage(this->source->toLocalFile());
auto image = QImage(this->source.toLocalFile());
if (this->imageRect.isValid()) {
image = image.copy(this->imageRect);
@@ -61,7 +62,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
}
if (image.isNull()) {
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source.toString();
return;
}
@@ -191,6 +192,8 @@ void ColorQuantizerOperation::run() {
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
ColorQuantizer::~ColorQuantizer() { this->cancelAsync(); }
void ColorQuantizer::componentComplete() {
this->componentCompleted = true;
if (!this->mSource.isEmpty()) this->quantizeAsync();
@@ -246,7 +249,7 @@ void ColorQuantizer::quantizeAsync() {
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
this->liveOperation = new ColorQuantizerOperation(
&this->mSource,
this->mSource,
this->mDepth,
this->mImageRect,
this->mRescaleSize
+6 -3
View File
@@ -7,6 +7,7 @@
#include <qqmlparserstatus.h>
#include <qrect.h>
#include <qrunnable.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
@@ -17,7 +18,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
void run() override;
void tryCancel();
@@ -43,7 +44,7 @@ private:
QAtomicInteger<bool> shouldCancel = false;
QList<QColor> colors;
QUrl* source;
QUrl source;
qreal maxDepth;
QRect imageRect;
qreal rescaleSize;
@@ -89,11 +90,13 @@ class ColorQuantizer
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
/// > reccommended to rescale, otherwise the quantization process will take much longer.
/// > recommended to rescale, otherwise the quantization process will take much longer.
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
public:
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
~ColorQuantizer() override;
Q_DISABLE_COPY_MOVE(ColorQuantizer);
void componentComplete() override;
void classBegin() override {}
+1 -1
View File
@@ -14,7 +14,7 @@
#include "generation.hpp"
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
class PixmapCacheIconEngine: public QIconEngine {
void paint(
QPainter* /*unused*/,
+20 -4
View File
@@ -257,9 +257,21 @@ void LogManager::filterCategory(QLoggingCategory* category) {
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
// clang-format on
} else if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
} else {
if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
}
// Hides virtual/override property warnings.
// Getting rid of this is blocked by https://qt-project.atlassian.net/browse/QTBUG-145977
// for internal Quickshell types, and may still be desired for shells that want to maintain
// compatibility with Qt versions prior to 6.11.
if (categoryName == QLatin1StringView("qt.qml.propertyCache.append")
&& !qEnvironmentVariableIsSet("QS_NO_FILTER_QT_LOGS"))
{
filter.warn = false;
}
}
for (const auto& rule: *instance->rules) {
@@ -292,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;
@@ -530,7 +546,7 @@ void ThreadLogging::initFs() {
Qt::QueuedConnection
);
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
}
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
+4
View File
@@ -138,6 +138,10 @@ public:
qsizetype oi = 0;
for (auto* object: newValues) {
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
// object may already be present further down (reorder case);
// drop the old row first so the same pointer is not inserted twice.
auto old = this->mValuesList.indexOf(object, oi);
if (old != -1) this->removeAt(old);
this->insertObject(object, oi);
}
+12 -9
View File
@@ -1,6 +1,7 @@
#include "paths.hpp"
#include <cerrno>
#include <cstdio>
#include <tuple>
#include <utility>
#include <fcntl.h>
@@ -9,7 +10,6 @@
#include <qdir.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qpair.h>
#include <qstandardpaths.h>
#include <qtenvironmentvariables.h>
#include <qtversionchecks.h>
@@ -412,10 +412,11 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
return true;
}
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths) << "Collecting instances from" << path;
auto liveInstances = QVector<InstanceLockInfo>();
auto mismatchedInstances = QVector<InstanceLockInfo>();
auto deadInstances = QVector<InstanceLockInfo>();
auto dir = QDir(path);
@@ -427,20 +428,22 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path;
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
if (info.pid == -1) {
deadInstances.push_back(info);
continue;
}
if (info.pid == -1) {
deadInstances.push_back(info);
} else {
liveInstances.push_back(info);
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
mismatchedInstances.push_back(info);
continue;
}
liveInstances.push_back(info);
} else {
qCDebug(logPaths) << "Skipped potential instance at" << path;
}
}
return qMakePair(liveInstances, deadInstances);
return {liveInstances, mismatchedInstances, deadInstances};
}
+4 -1
View File
@@ -1,4 +1,7 @@
#pragma once
#include <tuple>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdir.h>
#include <qpair.h>
@@ -29,7 +32,7 @@ public:
static QString ipcPath(const QString& id);
static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
collectInstances(const QString& path, const QString& display);
QDir* baseRunDir();
+3 -1
View File
@@ -93,6 +93,8 @@ void PopupAnchor::setItem(QQuickItem* item) {
QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
}
emit this->itemChanged();
}
void PopupAnchor::onWindowDestroyed() {
@@ -109,7 +111,7 @@ void PopupAnchor::onItemDestroyed() {
}
void PopupAnchor::onItemWindowChanged() {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem ? this->mItem->window() : nullptr)) {
this->setWindowInternal(window->proxy());
} else {
this->setWindowInternal(nullptr);
+8
View File
@@ -18,8 +18,10 @@
#include <qscreen.h>
#include <qtenvironmentvariables.h>
#include <qtmetamacros.h>
#include <qtversion.h>
#include <qtypes.h>
#include <qvariant.h>
#include <qversionnumber.h>
#include <qwindowdefs.h>
#include <unistd.h>
@@ -343,6 +345,12 @@ bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
return QuickshellGlobal::hasVersion(major, minor, QStringList());
}
bool QuickshellGlobal::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
auto* qsg = new QuickshellGlobal();
auto* generation = EngineGeneration::findEngineGeneration(engine);
+12 -1
View File
@@ -244,7 +244,7 @@ public:
/// it is assumed that all unreleased features are present. The unreleased feature list
/// may be omitted.
///
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
@@ -254,6 +254,17 @@ public:
/// > ```
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
/// Check if Qt's version is at least `major.minor`.
///
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
/// > //@ if hasVersion(6, 10)
/// > ...
/// > //@ endif
/// > ```
Q_INVOKABLE static bool hasQtVersion(qint32 major, qint32 minor);
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
+1 -1
View File
@@ -15,7 +15,7 @@
/// Monitor object useful for setting the monitor for a @@QsWindow
/// or querying information about the monitor.
///
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
/// > [!WARNING] If the monitor is disconnected, then any stored copies of its ShellMonitor will
/// > be marked as dangling and all properties will return default values.
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
///
+1 -1
View File
@@ -242,7 +242,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
}
QRegion PendingRegion::applyTo(const QRect& rect) const {
// if left as the default, dont combine it with the whole rect area, leave it as is.
// if left as the default, don't combine it with the whole rect area, leave it as is.
if (this->mIntersection == Intersection::Combine) {
return this->build();
} else {
+1 -1
View File
@@ -116,7 +116,7 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
auto* reloadable = qobject_cast<Reloadable*>(child);
if (reloadable != nullptr) {
if (reloadable->mReloadableId == reloadId) return reloadable;
// if not then don't check its children as thats a seperate reload scope.
// if not then don't check its children as that's a separate reload scope.
} else {
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
if (reloadable != nullptr) return reloadable;
+1 -1
View File
@@ -21,7 +21,7 @@ class Reloadable
/// previous state.
///
/// Simply keeping a stable identifier across config versions (saves) is
/// enough to help the reloader figure out which object in the old revision corrosponds to
/// enough to help the reloader figure out which object in the old revision corresponds to
/// this object in the current revision, and facilitate smoother reloading.
///
/// Note that identifiers are scoped, and will try to do the right thing in context.
+3 -3
View File
@@ -89,7 +89,7 @@ void QmlScanner::scanDir(const QDir& dir) {
QString qmldir;
auto stream = QTextStream(&qmldir);
// cant derive a module name if not in shell path
// can't derive a module name if not in shell path
if (path.startsWith(this->rootPath.path())) {
auto end = path.sliced(this->rootPath.path().length());
@@ -160,7 +160,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
if (!singleton && line == "pragma Singleton") {
singleton = true;
} else if (line.startsWith("import")) {
// we dont care about "import qs" as we always load the root folder
// we don't care about "import qs" as we always load the root folder
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
importCursor += 4;
QString path;
@@ -211,7 +211,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
mask = false;
}
if (!sourceMasked && mask) hideMask = true;
mask = sourceMasked || mask; // cant unmask if a nested if passes
mask = sourceMasked || mask; // can't unmask if a nested if passes
ifScopes.append(mask);
if (mask) isOverridden = true;
sourceMasked = mask;
+8
View File
@@ -2,6 +2,8 @@
#include <qcontainerfwd.h>
#include <qtenvironmentvariables.h>
#include <qtversion.h>
#include <qversionnumber.h>
#include "build.hpp"
@@ -20,6 +22,12 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
}
bool PreprocEnv::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QString PreprocEnv::env(const QString& variable) {
return qEnvironmentVariable(variable.toStdString().c_str());
}
+1
View File
@@ -12,6 +12,7 @@ class PreprocEnv: public QObject {
public:
Q_INVOKABLE static bool
hasVersion(int major, int minor, const QStringList& features = QStringList());
Q_INVOKABLE static bool hasQtVersion(int major, int minor);
Q_INVOKABLE static QString env(const QString& variable);
Q_INVOKABLE static bool isEnvSet(const QString& variable);
+111 -28
View File
@@ -4,39 +4,95 @@
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qjsvalueiterator.h>
#include <qlist.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtversionchecks.h>
#include <qtypes.h>
#include <qvariant.h>
void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->hasActiveIterators = true;
namespace {
struct RecursionPair {
const QJSValue* a;
const QJSValue* b;
const RecursionPair* previous;
};
bool qjsValueStructuralEq(
const QJSValue& a,
const QJSValue& b,
const RecursionPair* activePair = nullptr
) {
if (a.strictlyEquals(b)) return true;
if (!a.isObject() || !b.isObject() || a.isArray() != b.isArray()) return false;
for (const auto* pair = activePair; pair != nullptr; pair = pair->previous) {
if (a.strictlyEquals(*pair->a) || b.strictlyEquals(*pair->b)) return false;
}
const auto nextActivePair = RecursionPair {.a = &a, .b = &b, .previous = activePair};
auto aIter = QJSValueIterator(a);
auto bIter = QJSValueIterator(b);
while (aIter.hasNext()) {
if (!bIter.hasNext()) return false;
aIter.next();
bIter.next();
const auto aName = aIter.name();
const auto bName = bIter.name();
const auto aValue = aIter.value();
if (aName == bName) {
const auto bValue = bIter.value();
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
} else {
if (!b.hasOwnProperty(aName)) return false;
const auto bValue = b.property(aName);
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
}
}
return !bIter.hasNext();
}
} // namespace
bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
auto anyChanges = false;
this->isModifying = true;
this->mValues.reserve(newValues.size());
auto iter = this->mValues.begin();
auto newIter = newValues.begin();
// TODO: cache this
auto getCmpKey = [this](const QVariant& v) {
if (v.canConvert<QVariantMap>()) {
auto vMap = v.value<QVariantMap>();
if (vMap.contains(this->cmpKey)) {
return vMap.value(this->cmpKey);
}
auto comparisonValue = [this](const QJSValue& value) {
if (value.hasProperty(this->cmpKey)) return value.property(this->cmpKey);
return value;
};
auto valueCmp = [&, this](const QJSValue& a, const QJSValue& b) {
if (a.strictlyEquals(b)) return true;
const auto aValue = this->cmpKey.isEmpty() ? a : comparisonValue(a);
const auto bValue = this->cmpKey.isEmpty() ? b : comparisonValue(b);
if (this->mComparisonMode == ObjectComparison::Identity) {
return aValue.strictlyEquals(bValue);
} else {
return qjsValueStructuralEq(aValue, bValue);
}
return v;
};
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
else return a == b;
};
auto eqPredicate = [&](const QVariant& b) {
return [&](const QVariant& a) { return variantCmp(a, b); };
auto eqPredicate = [&](const QJSValue& b) {
return [&](const QJSValue& a) { return valueCmp(a, b); };
};
while (true) {
@@ -49,6 +105,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, endIndex);
this->mValues.erase(iter, this->mValues.end());
this->endRemoveRows();
anyChanges = true;
break;
} else if (iter == this->mValues.end()) {
@@ -59,9 +116,10 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginInsertRows(QModelIndex(), startIndex, endIndex);
this->mValues.append(newValues.sliced(startIndex));
this->endInsertRows();
anyChanges = true;
break;
} else if (!variantCmp(*newIter, *iter)) {
} else if (!valueCmp(*newIter, *iter)) {
auto oldIter = std::find_if(iter, this->mValues.end(), eqPredicate(*newIter));
if (oldIter != this->mValues.end()) {
@@ -81,6 +139,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, index - 1);
iter = this->mValues.erase(startIter, iter);
this->endRemoveRows();
anyChanges = true;
} else {
// Advance iters to capture a whole move sequence as a single operation if possible.
auto oldStartIter = oldIter;
@@ -88,7 +147,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
++oldIter;
++newIter;
} while (oldIter != this->mValues.end() && newIter != newValues.end()
&& variantCmp(*oldIter, *newIter));
&& valueCmp(*oldIter, *newIter));
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto oldStartIndex =
@@ -105,6 +164,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
iter = this->mValues.begin() + (index + len);
this->endMoveRows();
anyChanges = true;
}
} else {
auto startNewIter = newIter;
@@ -130,8 +190,9 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
std::move_backward(iter, this->mValues.end() - len, this->mValues.end());
iter = std::copy(startNewIter, newIter, iter);
this->endInsertRows();
anyChanges = true;
}
} else if (*newIter != *iter) {
} else if (!newIter->strictlyEquals(*iter)) {
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto index = first;
@@ -140,26 +201,42 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
++iter;
++newIter;
++index;
} while (iter != this->mValues.end() && newIter != newValues.end() && *newIter != *iter);
} while (iter != this->mValues.end() && newIter != newValues.end()
&& !newIter->strictlyEquals(*iter));
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(index - 1, 0, QModelIndex()),
{Qt::UserRole}
);
anyChanges = true;
} else {
++iter;
++newIter;
}
}
this->hasActiveIterators = false;
this->isModifying = false;
if (this->stagedValues.has_value()) {
auto values = *this->stagedValues;
this->stagedValues.reset();
anyChanges |= this->updateValuesUnique(values);
}
return anyChanges;
}
void ScriptModel::setValues(const QVariantList& newValues) {
if (newValues == this->mValues) return;
this->updateValuesUnique(newValues);
emit this->valuesChanged();
void ScriptModel::setValues(const QList<QJSValue>& newValues) {
// Re-entrant modification waits for the original modification to complete to avoid mishandled duplicates.
if (this->isModifying) {
this->stagedValues = newValues;
return;
}
auto changed = this->updateValuesUnique(newValues);
if (changed) emit this->valuesChanged();
}
void ScriptModel::setObjectProp(const QString& objectProp) {
@@ -169,6 +246,12 @@ void ScriptModel::setObjectProp(const QString& objectProp) {
emit this->objectPropChanged();
}
void ScriptModel::setComparisonMode(ObjectComparison::Enum comparisonMode) {
if (comparisonMode == this->mComparisonMode) return;
this->mComparisonMode = comparisonMode;
emit this->comparisonModeChanged();
}
qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->mValues.length());
@@ -176,7 +259,7 @@ qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
QVariant ScriptModel::data(const QModelIndex& index, qint32 role) const {
if (role != Qt::UserRole) return QVariant();
return this->mValues.at(index.row());
return this->mValues.at(index.row()).toVariant(QJSValue::RetainJSObjects);
}
QHash<int, QByteArray> ScriptModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; }
+42 -7
View File
@@ -1,10 +1,31 @@
#pragma once
#include <optional>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
///! @@ScriptModel value comparison mode.
namespace ObjectComparison { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum : quint8 {
/// Compare values using strict JavaScript equality.
Identity = 0,
/// Compare records and arrays structurally.
Structure = 1,
};
Q_ENUM_NS(Enum);
} // namespace ObjectComparison
///! QML model reflecting a javascript expression
/// ScriptModel is a QML [Data Model] that generates model operations based on changes
@@ -40,6 +61,7 @@
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
class ScriptModel: public QAbstractListModel {
Q_OBJECT;
// clang-format off
/// The list of values to reflect in the model.
/// > [!WARNING] ScriptModel currently only works with lists of *unique* values.
/// > There must not be any duplicates in the given list, or behavior of the model is undefined.
@@ -66,7 +88,8 @@ class ScriptModel: public QAbstractListModel {
/// > values: [...DesktopEntries.applications.values].sort(...)
/// > }
/// > ```
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
// TODO: use qjsobject for gc/free checks?
Q_PROPERTY(QList<QJSValue> values READ values WRITE setValues NOTIFY valuesChanged);
/// The property that javascript objects passed into the model will be compared with.
///
/// For example, if `objectProp` is `"myprop"` then `{ myprop: "a", other: "y" }` and
@@ -74,21 +97,30 @@ class ScriptModel: public QAbstractListModel {
///
/// Defaults to `""`, meaning no key.
Q_PROPERTY(QString objectProp READ objectProp WRITE setObjectProp NOTIFY objectPropChanged);
/// How values should be compared. Defaults to `ObjectComparison.Structure`.
///
/// Identity based comparison is faster if usable for a given model, and often
/// achievable with @@objectProp.
Q_PROPERTY(ObjectComparison::Enum comparisonMode READ comparisonMode WRITE setComparisonMode NOTIFY comparisonModeChanged);
// clang-format on
QML_ELEMENT;
public:
[[nodiscard]] QVariantList values() const {
[[nodiscard]] QList<QJSValue> values() const {
auto values = this->mValues;
// If not detached, the QML engine will invalidate iterators in updateValuesUnique.
if (this->hasActiveIterators) values.detach();
if (this->isModifying) values.detach();
return values;
}
void setValues(const QVariantList& newValues);
void setValues(const QList<QJSValue>& newValues);
[[nodiscard]] QString objectProp() const { return this->cmpKey; }
void setObjectProp(const QString& objectProp);
[[nodiscard]] ObjectComparison::Enum comparisonMode() const { return this->mComparisonMode; }
void setComparisonMode(ObjectComparison::Enum comparisonMode);
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
@@ -96,11 +128,14 @@ public:
signals:
void valuesChanged();
void objectPropChanged();
void comparisonModeChanged();
private:
QVariantList mValues;
QList<QJSValue> mValues;
QString cmpKey;
bool hasActiveIterators = false;
ObjectComparison::Enum mComparisonMode = ObjectComparison::Structure;
bool isModifying = false;
std::optional<QList<QJSValue>> stagedValues;
void updateValuesUnique(const QVariantList& newValues);
bool updateValuesUnique(const QList<QJSValue>& newValues);
};
+1
View File
@@ -8,3 +8,4 @@ qs_test(transformwatcher transformwatcher.cpp)
qs_test(ringbuffer ringbuf.cpp)
qs_test(scriptmodel scriptmodel.cpp)
qs_test(stacklist stacklist.cpp)
qs_test(objectmodel objectmodel.cpp)
+47
View File
@@ -0,0 +1,47 @@
#include "objectmodel.hpp"
#include <qlist.h>
#include <qobject.h>
#include <qtest.h>
#include <qtestcase.h>
#include "../model.hpp"
void TestObjectModel::diffUpdateInsertRemove() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c}));
// drop b, append d — relative order of survivors unchanged
model.diffUpdate({&a, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &c, &d}));
}
void TestObjectModel::diffUpdateReorder() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&d);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&d, &a, &b, &c}));
// pure permutation: same elements, different order. Previously turned
// [d,a,b,c] into [a,b,c,d,a,b,c] because the old row was never removed
// before inserting at the new index.
model.diffUpdate({&a, &b, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c, &d}));
}
QTEST_MAIN(TestObjectModel);
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <qobject.h>
#include <qtmetamacros.h>
class TestObjectModel: public QObject {
Q_OBJECT;
private slots:
static void diffUpdateInsertRemove();
static void diffUpdateReorder();
};
+156 -54
View File
@@ -4,10 +4,11 @@
#include <qabstractitemmodeltester.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qjsengine.h>
#include <qjsvalue.h>
#include <qlist.h>
#include <qlogging.h>
#include <qobject.h>
#include <qsignalspy.h>
#include <qstring.h>
#include <qtest.h>
#include <qtestcase.h>
@@ -15,8 +16,6 @@
#include "../scriptmodel.hpp"
using OpList = QList<ModelOperation>;
bool ModelOperation::operator==(const ModelOperation& other) const {
return other.operation == this->operation && other.index == this->index
&& other.length == this->length && other.destIndex == this->destIndex;
@@ -44,15 +43,44 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
return debug;
}
ChangeObserver::ChangeObserver(QAbstractItemModel* model) {
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ChangeObserver::onInsert);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ChangeObserver::onRemove);
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ChangeObserver::onMove);
}
void ChangeObserver::onInsert(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Insert, first, last - first + 1);
}
void ChangeObserver::onRemove(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Remove, first, last - first + 1);
}
void ChangeObserver::onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
this->mOperations
.emplaceBack(ModelOperation::Move, sourceStart, sourceEnd - sourceStart + 1, destStart);
}
// NOLINTNEXTLINE(misc-use-internal-linkage)
QDebug& operator<<(QDebug& debug, const QVariantList& list) {
auto str = QString();
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
QString str;
for (const auto& var: list) {
if (var.canConvert<QChar>()) {
str += var.value<QChar>();
if (var.isString()) {
str += var.toString();
} else {
qFatal() << "QVariantList debug overridden in test";
qFatal() << "QJSValueList debug overridden in test";
}
}
@@ -60,6 +88,18 @@ QDebug& operator<<(QDebug& debug, const QVariantList& list) {
return debug;
}
namespace {
bool qjsValueListsEqual(const QJSValueList& a, const QJSValueList& b) {
if (a.length() != b.length()) return false;
for (auto i = 0; i != a.length(); i++) {
if (!a.at(i).strictlyEquals(b.at(i))) return false;
}
return true;
}
} // namespace
void TestScriptModel::unique_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
@@ -90,7 +130,7 @@ void TestScriptModel::unique_data() {
QTest::addRow("move_overlapping")
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
// Ensure iterators arent skipping anything at the end of operations by performing
// Ensure iterators aren't skipping anything at the end of operations by performing
// multiple back to back.
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
@@ -117,65 +157,127 @@ void TestScriptModel::unique() {
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
auto strToVariantList = [](const QString& str) -> QVariantList {
QVariantList list;
auto strToJsValueList = [](const QString& str) -> QJSValueList {
QJSValueList list;
for (auto c: str) {
list.emplace_back(c);
list.emplace_back(QString(c));
}
return list;
};
auto oldlist = strToVariantList(oldstr);
auto newlist = strToVariantList(newstr);
auto oldList = strToJsValueList(oldstr);
auto newList = strToJsValueList(newstr);
auto model = ScriptModel();
ScriptModel model;
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
OpList actualOperations;
auto onInsert = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Insert, first, last - first + 1);
};
auto onRemove = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Remove, first, last - first + 1);
};
auto onMove = [&](const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
actualOperations << ModelOperation(
ModelOperation::Move,
sourceStart,
sourceEnd - sourceStart + 1,
destStart
);
};
QObject::connect(&model, &QAbstractItemModel::rowsInserted, &model, onInsert);
QObject::connect(&model, &QAbstractItemModel::rowsRemoved, &model, onRemove);
QObject::connect(&model, &QAbstractItemModel::rowsMoved, &model, onMove);
model.setValues(oldlist);
QCOMPARE_EQ(model.values(), oldlist);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
actualOperations,
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldlist.length())}})
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
actualOperations.clear();
observer.clear();
model.setValues(newlist);
QCOMPARE_EQ(model.values(), newlist);
QCOMPARE_EQ(actualOperations, operations);
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
}
void TestScriptModel::structuralEquality() {
ScriptModel model;
QJSEngine engine;
auto object = engine.newObject();
object.setProperty("foo", "bar");
object.setProperty("bar", "baz");
object.setProperty("subobject", engine.newObject());
auto replacement = engine.newObject();
replacement.setProperty("subobject", engine.newObject());
replacement.setProperty("bar", "baz");
replacement.setProperty("foo", "bar");
auto oldList = QList<QJSValue> {object};
auto newList = QList<QJSValue> {replacement};
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
observer.clear();
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), OpList());
auto structurallyEquals = [](const QJSValue& a, const QJSValue& b) {
ScriptModel model;
model.setValues({a});
auto observer = ChangeObserver(&model);
model.setValues({b});
return observer.operations().isEmpty();
};
auto different = engine.newObject();
different.setProperty("foo", "bar");
different.setProperty("bar", "baz");
auto differentSubobject = engine.newObject();
differentSubobject.setProperty("value", 1);
different.setProperty("subobject", differentSubobject);
QVERIFY(!structurallyEquals(object, different));
auto cycleA = engine.newObject();
cycleA.setProperty("self", cycleA);
auto cycleB = engine.newObject();
cycleB.setProperty("self", cycleB);
QVERIFY(!structurallyEquals(cycleA, cycleB));
const auto arrayA = engine.evaluate("[1, { value: 'x' }]");
const auto arrayB = engine.evaluate("[1, { value: 'x' }]");
QVERIFY(structurallyEquals(arrayA, arrayB));
auto sparseArrayA = engine.newArray(1);
auto sparseArrayB = engine.newArray(2);
QVERIFY(!structurallyEquals(sparseArrayA, sparseArrayB));
}
void TestScriptModel::comparisonModes() {
QJSEngine engine;
auto makeValue = [&engine]() {
auto value = engine.newObject();
value.setProperty("nested", engine.newObject());
return value;
};
ScriptModel model;
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Structure);
model.setValues({makeValue()});
model.setComparisonMode(ObjectComparison::Identity);
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Identity);
auto observer = ChangeObserver(&model);
model.setValues({makeValue()});
QVERIFY(!observer.operations().isEmpty());
model.setComparisonMode(ObjectComparison::Structure);
observer.clear();
model.setValues({makeValue()});
QCOMPARE_EQ(observer.operations(), OpList());
}
QTEST_MAIN(TestScriptModel);
+27
View File
@@ -1,6 +1,8 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qdebug.h>
#include <qlist.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -26,6 +28,29 @@ struct ModelOperation {
[[nodiscard]] bool operator==(const ModelOperation& other) const;
};
using OpList = QList<ModelOperation>;
class ChangeObserver: public QObject {
public:
explicit ChangeObserver(QAbstractItemModel* model);
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
void clear() { this->mOperations.clear(); }
private:
void onInsert(const QModelIndex& parent, int first, int last);
void onRemove(const QModelIndex& parent, int first, int last);
void onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
);
OpList mOperations;
};
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
class TestScriptModel: public QObject {
@@ -34,4 +59,6 @@ class TestScriptModel: public QObject {
private slots:
static void unique_data(); // NOLINT
static void unique();
static void structuralEquality();
static void comparisonModes();
};
+1 -1
View File
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
{
if (this->mInstances.contains(variant)) {
continue; // we dont need to recreate this one
continue; // we don't need to recreate this one
}
auto variantMap = QVariantMap();
+1 -1
View File
@@ -43,7 +43,7 @@ class Variants: public Reloadable {
Q_OBJECT;
/// The component to create instances of.
///
/// The delegate should define a `modelData` property that will be popuplated with a value
/// The delegate should define a `modelData` property that will be populated with a value
/// from the @@model.
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
/// The list of sets of properties to create instances with.
+7
View File
@@ -301,6 +301,13 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
&DBusMenu::onLayoutUpdated
);
QObject::connect(
this->interface,
&DBusMenuInterface::ItemsPropertiesUpdated,
this,
&DBusMenu::onItemPropertiesUpdated
);
this->properties.setInterface(this->interface);
this->properties.updateAllViaGetAll();
}
+1 -1
View File
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
return;
}
// make sure we dont miss any delimiters in the buffer if the delimiter changes
// make sure we don't miss any delimiters in the buffer if the delimiter changes
if (this->mSplitMarkerChanged) {
this->mSplitMarkerChanged = false;
this->parseBytes(buffer, buffer);
+1
View File
@@ -495,6 +495,7 @@ void FileView::updateWatchedFiles() {
// If inotify events are sent to the watcher after deletion and deleteLater
// isn't used, a use after free in the QML engine will occur.
if (this->watcher) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+3 -3
View File
@@ -29,9 +29,9 @@ class FileViewError: public QObject {
public:
enum Enum : quint8 {
/// No error occured.
/// No error occurred.
Success = 0,
/// An unknown error occured. Check the logs for details.
/// An unknown error occurred. Check the logs for details.
Unknown = 1,
/// The file to read does not exist.
FileNotFound = 2,
@@ -204,7 +204,7 @@ class FileView: public QObject {
///
/// > [!NOTE] This works by creating another file with the desired content, and renaming
/// > it over the existing file if successful.
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableAtomicWrites);
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY atomicWritesChanged BINDABLE bindableAtomicWrites);
/// If true (default), read or write errors will be printed to the quickshell logs.
/// If false, all known errors will not be printed.
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
+67 -154
View File
@@ -1,181 +1,94 @@
#include "ipc.hpp"
#include <cstring>
#include <utility>
#include <qcolor.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtypes.h>
#include <qvariant.h>
namespace qs::io::ipc {
const VoidIpcType VoidIpcType::INSTANCE {};
const StringIpcType StringIpcType::INSTANCE {};
const IntIpcType IntIpcType::INSTANCE {};
const BoolIpcType BoolIpcType::INSTANCE {};
const DoubleIpcType DoubleIpcType::INSTANCE {};
const ColorIpcType ColorIpcType::INSTANCE {};
bool IpcValueSlot::isSupported(const QMetaType& type) { return IpcValueSlot::typeName(type); }
void* IpcType::copyStorage(const void* data) const {
auto* storage = this->createStorage();
memcpy(storage, data, this->size());
return storage;
}
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
return nullptr;
}
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
this->mType = other.mType;
this->storage = other.storage;
other.mType = nullptr;
other.storage = nullptr;
return *this;
}
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
const IpcType* IpcTypeSlot::type() const { return this->mType; }
void* IpcTypeSlot::get() {
if (this->storage == nullptr) {
this->storage = this->mType->createStorage();
}
return this->storage;
}
QGenericArgument IpcTypeSlot::asGenericArgument() {
if (this->mType) {
return QGenericArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericArgument();
const char* IpcValueSlot::typeName(const QMetaType& type) {
switch (type.id()) {
case QMetaType::Void: return "void";
case QMetaType::QString: return "string";
case QMetaType::Int: return "int";
case QMetaType::Bool: return "bool";
case QMetaType::Double: return "real";
case QMetaType::QColor: return "color";
default: return nullptr;
}
}
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
if (this->mType) {
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericReturnArgument();
const char* IpcValueSlot::name() const {
return this->mValue.isValid() ? IpcValueSlot::typeName(this->mValue.metaType()) : "void";
}
bool IpcValueSlot::isVoid() const { return !this->mValue.isValid(); }
const QVariant& IpcValueSlot::value() const { return this->mValue; }
bool IpcValueSlot::setString(const QString& string) {
switch (this->mValue.typeId()) {
case QMetaType::QString: this->mValue = QVariant::fromValue(string); return true;
case QMetaType::Int: {
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::Bool: {
if (string == "true" || string == "false") {
this->mValue = QVariant::fromValue(string == "true");
return true;
}
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value != 0);
return ok;
}
case QMetaType::Double: {
auto ok = false;
auto value = string.toDouble(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::QColor: {
auto value = QColor::fromString(string);
if (!value.isValid() && !string.startsWith('#')) value = QColor::fromString('#' % string);
if (value.isValid()) this->mValue = QVariant::fromValue(value);
return value.isValid();
}
default: return false;
}
}
void IpcTypeSlot::replace(void* value) {
if (this->storage != nullptr) {
this->mType->destroyStorage(this->storage);
void IpcValueSlot::setValue(QVariant value) { this->mValue = std::move(value); }
QString IpcValueSlot::toString() const {
switch (this->mValue.typeId()) {
case QMetaType::UnknownType: return "";
case QMetaType::QString: return this->mValue.value<QString>();
case QMetaType::Int: return QString::number(this->mValue.value<int>());
case QMetaType::Bool: return this->mValue.value<bool>() ? "true" : "false";
case QMetaType::Double: return QString::number(this->mValue.value<double>());
case QMetaType::QColor: return this->mValue.value<QColor>().name(QColor::HexArgb);
default: return "";
}
this->storage = value;
}
void IpcTypeSlot::replace(const QVariant& value) {
this->replace(this->mType->copyStorage(value.constData()));
QGenericArgument IpcValueSlot::asGenericArgument() const {
return QGenericArgument(this->mValue.metaType().name(), this->mValue.constData());
}
const char* VoidIpcType::name() const { return "void"; }
const char* VoidIpcType::genericArgumentName() const { return "void"; }
qsizetype VoidIpcType::size() const { return 0; }
// string
const char* StringIpcType::name() const { return "string"; }
const char* StringIpcType::genericArgumentName() const { return "QString"; }
qsizetype StringIpcType::size() const { return sizeof(QString); }
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
void* StringIpcType::createStorage() const { return new QString(); }
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
// int
const char* IntIpcType::name() const { return "int"; }
const char* IntIpcType::genericArgumentName() const { return "int"; }
qsizetype IntIpcType::size() const { return sizeof(int); }
void* IntIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toInt(&ok);
return ok ? new int(v) : nullptr;
QGenericReturnArgument IpcValueSlot::asGenericReturnArgument() {
if (!this->mValue.isValid()) return QGenericReturnArgument();
return QGenericReturnArgument(this->mValue.metaType().name(), this->mValue.data());
}
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
void* IntIpcType::createStorage() const { return new int(); }
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
// bool
const char* BoolIpcType::name() const { return "bool"; }
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
qsizetype BoolIpcType::size() const { return sizeof(bool); }
void* BoolIpcType::fromString(const QString& string) const {
if (string == "true") return new bool(true);
if (string == "false") return new bool(false);
auto isInt = false;
auto iv = string.toInt(&isInt);
return isInt ? new bool(iv != 0) : nullptr;
}
QString BoolIpcType::toString(void* slot) const {
return *static_cast<bool*>(slot) ? "true" : "false";
}
void* BoolIpcType::createStorage() const { return new bool(); }
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
// double
const char* DoubleIpcType::name() const { return "real"; }
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
qsizetype DoubleIpcType::size() const { return sizeof(double); }
void* DoubleIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toDouble(&ok);
return ok ? new double(v) : nullptr;
}
QString DoubleIpcType::toString(void* slot) const {
return QString::number(*static_cast<double*>(slot));
}
void* DoubleIpcType::createStorage() const { return new double(); }
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
// color
const char* ColorIpcType::name() const { return "color"; }
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
void* ColorIpcType::fromString(const QString& string) const {
auto color = QColor::fromString(string);
if (!color.isValid() && !string.startsWith('#')) {
color = QColor::fromString('#' % string);
}
return color.isValid() ? new QColor(color) : nullptr;
}
QString ColorIpcType::toString(void* slot) const {
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
}
void* ColorIpcType::createStorage() const { return new bool(); }
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
QString WireFunctionDefinition::toString() const {
QString paramString;
for (const auto& [name, type]: this->arguments) {
+15 -110
View File
@@ -1,130 +1,35 @@
#pragma once
#include <qcontainerfwd.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "../ipc/ipc.hpp"
namespace qs::io::ipc {
class IpcTypeSlot;
class IpcType {
class IpcValueSlot {
public:
IpcType() = default;
virtual ~IpcType() = default;
IpcType(const IpcType&) = default;
IpcType(IpcType&&) = default;
IpcType& operator=(const IpcType&) = default;
IpcType& operator=(IpcType&&) = default;
IpcValueSlot() = default;
explicit IpcValueSlot(QMetaType type): mValue(type) {}
[[nodiscard]] virtual const char* name() const = 0;
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
[[nodiscard]] virtual qsizetype size() const = 0;
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
virtual void destroyStorage(void* /*slot*/) const {}
void* copyStorage(const void* data) const;
[[nodiscard]] static bool isSupported(const QMetaType& type);
[[nodiscard]] static const char* typeName(const QMetaType& type);
[[nodiscard]] const char* name() const;
[[nodiscard]] bool isVoid() const;
[[nodiscard]] const QVariant& value() const;
static const IpcType* ipcType(const QMetaType& metaType);
};
bool setString(const QString& string);
void setValue(QVariant value);
[[nodiscard]] QString toString() const;
class IpcTypeSlot {
public:
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
~IpcTypeSlot();
Q_DISABLE_COPY(IpcTypeSlot);
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
[[nodiscard]] const IpcType* type() const;
[[nodiscard]] void* get();
[[nodiscard]] QGenericArgument asGenericArgument();
[[nodiscard]] QGenericArgument asGenericArgument() const;
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
void replace(void* value);
void replace(const QVariant& value);
private:
const IpcType* mType = nullptr;
void* storage = nullptr;
};
class VoidIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
static const VoidIpcType INSTANCE;
};
class StringIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const StringIpcType INSTANCE;
};
class IntIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const IntIpcType INSTANCE;
};
class BoolIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const BoolIpcType INSTANCE;
};
class DoubleIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const DoubleIpcType INSTANCE;
};
class ColorIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const ColorIpcType INSTANCE;
QVariant mValue;
};
struct WireFunctionDefinition {
+3 -3
View File
@@ -173,7 +173,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
func->invoke(handler, storage);
resp << Completed {
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
.isVoid = func->returnType.isVoid(),
.returnValue = storage.getReturnStr(),
};
} else {
@@ -269,11 +269,11 @@ void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
return;
}
auto slot = IpcTypeSlot(prop->type);
auto slot = IpcValueSlot(prop->type);
prop->read(handler, slot);
resp << PropertyValue {
.value = slot.type()->toString(slot.get()),
.value = slot.toString(),
};
} else {
conn->respond(StringCallResponse(NoCurrentGeneration()));
+36 -39
View File
@@ -34,9 +34,8 @@ bool IpcFunction::resolve(QString& error) {
for (auto i = 0; i < this->method.parameterCount(); i++) {
const auto& metaType = this->method.parameterMetaType(i);
const auto* type = IpcType::ipcType(metaType);
if (type == nullptr) {
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
.arg(i + 1)
.arg(this->method.parameterNames().value(i))
@@ -45,23 +44,20 @@ bool IpcFunction::resolve(QString& error) {
return false;
}
this->argumentTypes.append(type);
this->argumentTypes.emplaceBack(metaType);
}
const auto& metaType = this->method.returnMetaType();
const auto* type = IpcType::ipcType(metaType);
auto metaType = this->method.returnMetaType();
if (type == nullptr) {
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) metaType = QMetaType::fromType<void>();
if (type == nullptr) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
this->returnType = type;
this->returnType = IpcValueSlot(metaType);
return true;
}
@@ -92,52 +88,53 @@ QString IpcFunction::toString() const {
QString paramString;
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i).name();
if (i + 1 != this->argumentTypes.length()) {
paramString += ", ";
}
}
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType.name();
}
WireFunctionDefinition IpcFunction::wireDef() const {
WireFunctionDefinition wire;
wire.name = this->method.name();
wire.returnType = this->returnType->name();
wire.returnType = this->returnType.name();
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i).name());
}
return wire;
}
bool IpcProperty::resolve(QString& error) {
this->type = IpcType::ipcType(this->property.metaType());
const auto& metaType = this->property.metaType();
if (!this->type) {
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type %1 cannot be used across IPC.").arg(metaType.name());
return false;
}
this->type = metaType;
return true;
}
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
slot.replace(this->property.read(target));
void IpcProperty::read(QObject* target, IpcValueSlot& slot) const {
slot.setValue(this->property.read(target));
}
QString IpcProperty::toString() const {
return QString("property ") % this->property.name() % ": " % this->type->name();
return QString("property ") % this->property.name() % ": " % IpcValueSlot::typeName(this->type);
}
WirePropertyDefinition IpcProperty::wireDef() const {
WirePropertyDefinition wire;
wire.name = this->property.name();
wire.type = this->type->name();
wire.type = IpcValueSlot::typeName(this->type);
return wire;
}
@@ -217,21 +214,15 @@ IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function
}
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
auto& slot = this->argumentSlots.at(i);
auto* data = slot.type()->fromString(value);
slot.replace(data);
return data != nullptr;
return this->argumentSlots.at(i).setString(value);
}
QString IpcCallStorage::getReturnStr() {
return this->returnSlot.type()->toString(this->returnSlot.get());
}
QString IpcCallStorage::getReturnStr() { return this->returnSlot.toString(); }
IpcHandler::~IpcHandler() {
if (this->registeredState.enabled) {
this->targetState.enabled = false;
this->updateRegistration(true);
this->updateRegistration();
}
}
@@ -310,28 +301,34 @@ void IpcHandler::onSignalTriggered(const QString& signal, const QString& value)
-> triggered(this->registeredState.target, signal, value);
}
void IpcHandler::updateRegistration(bool destroying) {
void IpcHandler::updateRegistration() {
if (!this->complete) return;
auto* generation = EngineGeneration::findObjectGeneration(this);
IpcHandlerRegistry* registry = nullptr;
if (this->registeredState.enabled) {
registry = this->registry.get();
} else {
auto* generation = EngineGeneration::findObjectGeneration(this);
if (!generation) {
if (!destroying) {
if (!generation) {
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
return;
}
return;
registry = IpcHandlerRegistry::forGeneration(generation);
}
auto* registry = IpcHandlerRegistry::forGeneration(generation);
if (!registry) return;
if (this->registeredState.enabled) {
registry->deregisterHandler(this);
this->registry = nullptr;
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
}
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
registry->registerHandler(this);
this->registry = registry;
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
}
}
+16 -9
View File
@@ -11,6 +11,7 @@
#include <qmetaobject.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qpointer.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qtclasshelpermacros.h>
@@ -37,8 +38,8 @@ public:
[[nodiscard]] WireFunctionDefinition wireDef() const;
QMetaMethod method;
QVector<const IpcType*> argumentTypes;
const IpcType* returnType = nullptr;
QVector<IpcValueSlot> argumentTypes;
IpcValueSlot returnType;
};
class IpcCallStorage {
@@ -49,8 +50,8 @@ public:
[[nodiscard]] QString getReturnStr();
private:
std::vector<IpcTypeSlot> argumentSlots;
IpcTypeSlot returnSlot;
std::vector<IpcValueSlot> argumentSlots;
IpcValueSlot returnSlot;
friend class IpcFunction;
};
@@ -60,13 +61,13 @@ public:
explicit IpcProperty(QMetaProperty property): property(property) {}
bool resolve(QString& error);
void read(QObject* target, IpcTypeSlot& slot) const;
void read(QObject* target, IpcValueSlot& slot) const;
[[nodiscard]] QString toString() const;
[[nodiscard]] WirePropertyDefinition wireDef() const;
QMetaProperty property;
const IpcType* type = nullptr;
QMetaType type;
};
class IpcSignalListener: public QObject {
@@ -185,7 +186,7 @@ class IpcHandlerRegistry;
///
/// function getRadius(): int { return rect.radius; }
///
/// signal radiusChanged(newRadius: int);
/// signal radiusChanged(newRadius: int);
/// }
/// }
/// ```
@@ -254,7 +255,7 @@ public slots:
void onSignalTriggered(const QString& signal, const QString& value) const;
private:
void updateRegistration(bool destroying = false);
void updateRegistration();
struct RegistrationState {
explicit RegistrationState(bool enabled = false): enabled(enabled) {}
@@ -271,10 +272,16 @@ private:
QHash<QString, IpcProperty> propertyMap;
QHash<QString, IpcSignal> signalMap;
QPointer<IpcHandlerRegistry> registry;
friend class IpcHandlerRegistry;
};
class IpcHandlerRegistry: public EngineGenerationExt {
class IpcHandlerRegistry
: public QObject
, public EngineGenerationExt {
Q_OBJECT;
public:
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
+5 -5
View File
@@ -225,15 +225,15 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
const auto& jsonValue = array.at(i);
if (jsonValue.isObject()) {
if (isNew) {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
} else {
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
currentValue->setParent(this);
this->createdObjects.push_back(currentValue);
} else {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
}
this->deserializeRec(
+2 -2
View File
@@ -96,7 +96,7 @@ class SocketServer: public Reloadable {
Q_OBJECT;
/// If the socket server is currently active. Defaults to false.
///
/// Setting this to false will destory all active connections and delete
/// Setting this to false will destroy all active connections and delete
/// the socket file on disk.
///
/// If path is empty setting this property will have no effect.
@@ -105,7 +105,7 @@ class SocketServer: public Reloadable {
///
/// Setting this property while the server is active will have no effect.
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
/// Connection handler component. Must creeate a @@Socket.
/// Connection handler component. Must create a @@Socket.
///
/// The created socket should not set @@connected or @@path or the incoming
/// socket connection will be dropped (they will be set by the socket server.)
+11 -3
View File
@@ -105,11 +105,19 @@ IpcClient::IpcClient(const QString& path) {
bool IpcClient::isConnected() const { return this->socket.isValid(); }
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
void IpcClient::waitForDisconnected() {
this->waitingForDisconnect = true;
this->socket.waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
void IpcClient::kill() {
this->sendMessage(IpcCommand(IpcKillCommand()));
this->waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) const {
if (this->waitingForDisconnect && error == QLocalSocket::PeerClosedError) return;
qCCritical(logIpc) << "Socket Error" << error;
}
+5 -2
View File
@@ -19,7 +19,7 @@
template <typename... Types>
constexpr void assertSerializable() {
// monostate being zero ensures transactional reads wont break
// monostate being zero ensures transactional reads won't break
static_assert(
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
"Serialization of variants without std::monostate at index 0 is disallowed."
@@ -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
+72 -34
View File
@@ -12,7 +12,6 @@
#include <qdebug.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qguiapplication.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
@@ -120,7 +119,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
return -1;
}
} else {
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
qCCritical(logBare) << "Could not open manifest at path" << *cmd.config.manifest;
return -1;
}
} else {
@@ -165,7 +164,12 @@ void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
});
};
int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallback = false) {
int selectInstance(
CommandState& cmd,
InstanceLockInfo* instance,
bool deadFallback = false,
bool logHelp = true
) {
auto* basePath = QsPaths::instance()->baseRunDir();
if (!basePath) return -1;
@@ -174,18 +178,22 @@ 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()) {
path = basePath->filePath("by-pid");
auto [liveInstances, deadInstances] =
auto [liveInstances, mismatchedInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
liveInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
mismatchedInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
deadInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
@@ -193,27 +201,43 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) {
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances 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;
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
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 (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
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;
@@ -230,7 +254,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
path = QDir(basePath->filePath("by-path")).filePath(pathId);
auto [liveInstances, deadInstances] =
auto [liveInstances, mismatchedInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
auto instances = liveInstances;
@@ -244,16 +268,29 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
);
if (instances.isEmpty()) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
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:";
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;
}
@@ -314,7 +351,7 @@ int listInstances(CommandState& cmd) {
path = QDir(basePath->filePath("by-path")).filePath(pathId);
}
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
auto [liveInstances, mismatchedInstances, deadInstances] = QsPaths::collectInstances(
path,
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
);
@@ -436,7 +473,7 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
{
InstanceLockInfo info;
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
if (cmd.misc.noDuplicate && selectInstance(cmd, &info, false, false) == 0) {
qCInfo(logBare) << "An instance of this configuration is already running.";
return 0;
}
@@ -547,16 +584,17 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
}
QString getDisplayConnection() {
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
// Doesn't actually have to match what qt picks, but will 99% of the time.
// Running quickshell under wayland in x11 mode doesn't really make any sense.
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
auto xDisplay = qEnvironmentVariable("DISPLAY");
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
return "wayland," + wlDisplay;
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
return "x11," + xDisplay;
if (!wlDisplay.isEmpty()) {
return "wayland/" + wlDisplay;
} else if (!xDisplay.isEmpty()) {
return "x11/" + xDisplay;
} else {
return "unk," + QGuiApplication::platformName();
return "unk";
}
}
+23 -13
View File
@@ -76,6 +76,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
bool useSystemStyle = false;
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
QHash<QString, QString> envOverrides;
QHash<QString, QString> defaultEnv;
QString appId = qEnvironmentVariable("QS_APP_ID");
bool dropExpensiveFonts = false;
QString dataDir;
@@ -89,25 +90,30 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
if (line.startsWith("//@ pragma ")) {
auto pragma = line.sliced(11).trimmed();
if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("Env ")) {
auto envPragma = pragma.sliced(4);
auto splitIdx = envPragma.indexOf('=');
auto isEnv = pragma.startsWith("Env ");
auto isDefaultEnv = pragma.startsWith("DefaultEnv ");
if (isEnv || isDefaultEnv) {
auto content = pragma.sliced(isDefaultEnv ? 11 : 4);
auto splitIdx = content.indexOf('=');
if (splitIdx == -1) {
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
return -1;
}
auto var = envPragma.sliced(0, splitIdx).trimmed();
auto val = envPragma.sliced(splitIdx + 1).trimmed();
pragmas.envOverrides.insert(var, val);
} else if (pragma.startsWith("AppId ")) {
auto var = content.sliced(0, splitIdx).trimmed();
auto val = content.sliced(splitIdx + 1).trimmed();
if (isDefaultEnv) pragmas.defaultEnv.insert(var, val);
else pragmas.envOverrides.insert(var, val);
} else if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("AppId ")) {
pragmas.appId = pragma.sliced(6).trimmed();
} else if (pragma.startsWith("ShellId ")) {
shellId = pragma.sliced(8).trimmed();
@@ -174,6 +180,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
}
for (auto [var, val]: pragmas.defaultEnv.asKeyValueRange()) {
if (!qEnvironmentVariableIsSet(var.toUtf8())) qputenv(var.toUtf8(), val.toUtf8());
}
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
qputenv(var.toUtf8(), val.toUtf8());
}
+17 -10
View File
@@ -27,20 +27,28 @@ namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
#if CRASH_HANDLER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
auto dumpPid = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
if (!lastInfoFdStr.isEmpty()) {
auto lastInfoFd = lastInfoFdStr.toInt();
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
RelaunchInfo info;
{
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
}
file.seek(0);
auto ds = QDataStream(&file);
ds >> info;
}
file.seek(0);
auto ds = QDataStream(&file);
RelaunchInfo info;
ds >> info;
qunsetenv("__QUICKSHELL_CRASH_INFO_FD");
qunsetenv("__QUICKSHELL_CRASH_DUMP_PID");
qunsetenv("__QUICKSHELL_CRASH_SIGNAL");
LogManager::init(
!info.noColor,
@@ -50,8 +58,7 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
info.logRules
);
qCritical().nospace() << "Quickshell has crashed under pid "
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
qCritical().nospace() << "Quickshell has crashed under pid " << dumpPid
<< " (Coredumps will be available under that pid.)";
qCritical() << "Further crash information is stored under"
+2
View File
@@ -3,8 +3,10 @@ add_subdirectory(nm)
qt_add_library(quickshell-network STATIC
network.cpp
device.cpp
wired.cpp
wifi.cpp
enums.cpp
qml.cpp
)
target_include_directories(quickshell-network PRIVATE
+4
View File
@@ -9,6 +9,7 @@
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
@@ -45,4 +46,7 @@ void NetworkDevice::disconnect() {
this->requestDisconnect();
}
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
+17 -2
View File
@@ -7,12 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! A network device.
/// The @@type property may be used to determine if this device is a @@WifiDevice.
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
class NetworkDevice: public QObject {
Q_OBJECT;
QML_ELEMENT;
@@ -20,11 +22,17 @@ class NetworkDevice: public QObject {
// clang-format off
/// The device type.
///
/// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
/// When the device type is `Wired`, the device object is a @@WiredDevice.
/// connection and scanning.
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// A list of available or connected networks for this device.
///
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
/// True if the device is connected.
@@ -45,6 +53,10 @@ public:
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
virtual void networkAdded(Network* net);
virtual void networkRemoved(Network* net);
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QString name() const { return this->bName; }
@@ -69,6 +81,9 @@ signals:
void nmManagedChanged();
void autoconnectChanged();
protected:
ObjectModel<Network> mNetworks {this};
private:
DeviceType::Enum mType;
// clang-format off
+2 -1
View File
@@ -38,7 +38,7 @@ QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) {
switch (reason) {
case Unknown: return QStringLiteral("Unknown");
case NoSecrets: return QStringLiteral("Secrets were required but not provided");
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant diconnected");
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant disconnected");
case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed");
case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate");
case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found");
@@ -50,6 +50,7 @@ QString DeviceType::toString(DeviceType::Enum type) {
switch (type) {
case None: return QStringLiteral("None");
case Wifi: return QStringLiteral("Wifi");
case Wired: return QStringLiteral("Wired");
default: return QStringLiteral("Unknown");
}
}
+1
View File
@@ -98,6 +98,7 @@ public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
+2
View File
@@ -1,9 +1,11 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"qml.hpp",
"network.hpp",
"device.hpp",
"wifi.hpp",
"wired.hpp",
"enums.hpp",
"nm/settings.hpp",
]
+4 -65
View File
@@ -6,12 +6,9 @@
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
#include "nm/settings.hpp"
namespace qs::network {
@@ -20,68 +17,10 @@ namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
Network::Network(QString name, NetworkDevice* device, QObject* parent)
: QObject(parent)
, bName(std::move(name))
, mDevice(device) {
this->bStateChanging.setBinding([this] {
auto state = this->bState.value();
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
+17 -143
View File
@@ -7,147 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
namespace qs::network {
class NetworkDevice;
}
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
namespace qs::network {
///! A network.
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
@@ -158,11 +25,13 @@ class Network: public QObject {
// clang-format off
/// The name of the network.
Q_PROPERTY(QString name READ name CONSTANT);
/// A list of NetworkManager connnection settings profiles for this network.
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// The device this network belongs to.
Q_PROPERTY(NetworkDevice* device READ device CONSTANT);
/// A list of NetworkManager connection settings profiles for this network.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_PROPERTY(QList<NMSettings*> nmSettings READ nmSettings NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
/// True if the network is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// True if the wifi network has known connection settings saved.
@@ -174,7 +43,7 @@ class Network: public QObject {
// clang-format on
public:
explicit Network(QString name, QObject* parent = nullptr);
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network.
///
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
@@ -194,7 +63,9 @@ public:
void settingsRemoved(NMSettings* settings);
// clang-format off
[[nodiscard]] QString name() const { return this->mName; }
[[nodiscard]] QString name() const { return this->bName; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
QBindable<bool> bindableConnected() { return &this->bConnected; }
@@ -208,6 +79,7 @@ signals:
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
void connectionFailed(ConnectionFailReason::Enum reason);
void nameChanged();
void connectedChanged();
void knownChanged();
void stateChanged();
@@ -219,15 +91,17 @@ signals:
QSDOC_HIDE void requestForget();
protected:
QString mName;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, QList<NMSettings*>, bNmSettings, &Network::nmSettingsChanged);
// clang-format on
private:
NetworkDevice* mDevice;
};
} // namespace qs::network
+13
View File
@@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
dbus_nm_wireless
)
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES
CLASSNAME DBusNMWiredProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Device.Wired.xml
dbus_nm_wired
)
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
CLASSNAME DBusNMAccessPointProxy
NO_NAMESPACE TRUE
@@ -67,8 +77,10 @@ qt_add_library(quickshell-network-nm STATIC
settings.cpp
accesspoint.cpp
wireless.cpp
wired.cpp
utils.cpp
dbus_types.cpp
network.cpp
enums.hpp
${NM_DBUS_INTERFACES}
)
@@ -79,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
qs_add_link_dependencies(quickshell-network-nm quickshell-network)
+1 -1
View File
@@ -7,7 +7,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "../enums.hpp"
#include "dbus_nm_accesspoint.h"
#include "enums.hpp"
+7 -68
View File
@@ -15,15 +15,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "wired.hpp"
#include "wireless.hpp"
namespace qs::network {
@@ -133,6 +131,7 @@ void NetworkManager::registerDevice(const QString& path) {
switch (type) {
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break;
default: break;
}
@@ -147,8 +146,9 @@ void NetworkManager::registerDevice(const QString& path) {
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
// clang-format on
this->registerFrontendDevice(type, dev);
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
emit this->deviceAdded(dev->frontend());
});
}
} else {
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
@@ -160,67 +160,6 @@ void NetworkManager::registerDevice(const QString& path) {
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
}
void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) {
NetworkDevice* frontendDev = nullptr;
switch (type) {
case NMDeviceType::Wifi: {
auto* frontendWifiDev = new WifiDevice(dev);
auto* wifiDev = qobject_cast<NMWirelessDevice*>(dev);
// Bind WifiDevice-specific properties
auto translateMode = [wifiDev]() {
switch (wifiDev->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
// clang-format off
frontendWifiDev->bindableMode().setBinding(translateMode);
wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); });
QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded);
QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved);
// clang-format on
frontendDev = frontendWifiDev;
break;
}
default: return;
}
// Bind generic NetworkDevice properties
auto translateState = [dev]() {
switch (dev->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
frontendDev->bindableState().setBinding(translateState);
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
frontendDev->bindableNmManaged().setBinding([dev]() { return dev->managed(); });
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetNmManaged, dev, &NMDevice::setManaged);
// clang-format on
this->mFrontendDevices.insert(dev->path(), frontendDev);
emit this->deviceAdded(frontendDev);
}
void NetworkManager::removeFrontendDevice(NMDevice* dev) {
auto* frontendDev = this->mFrontendDevices.take(dev->path());
if (frontendDev) {
emit this->deviceRemoved(frontendDev);
frontendDev->deleteLater();
}
}
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
this->registerDevice(path.path());
}
@@ -235,7 +174,7 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
this->mDevices.erase(iter);
if (dev) {
qCDebug(logNetworkManager) << "Device removed:" << path.path();
this->removeFrontendDevice(dev);
emit this->deviceRemoved(dev->frontend());
delete dev;
}
}
+1 -4
View File
@@ -8,7 +8,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../network.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_types.hpp"
#include "device.hpp"
@@ -70,11 +70,8 @@ private:
void init();
void registerDevices();
void registerDevice(const QString& path);
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
void removeFrontendDevice(NMDevice* dev);
QHash<QString, NMDevice*> mDevices;
QHash<QString, NetworkDevice*> mFrontendDevices;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
+48
View File
@@ -14,9 +14,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
@@ -49,6 +53,29 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
this->deviceProperties.updateAllViaGetAll();
}
void NMDevice::bindFrontend(NetworkDevice* frontend) {
auto translateState = [this]() {
switch (this->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontend->bindableName().setBinding([this]() { return this->interface(); });
frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); });
frontend->bindableState().setBinding(translateState);
frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); });
frontend->bindableNmManaged().setBinding([this]() { return this->managed(); });
QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect);
QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged);
QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded);
QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved);
}
void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) {
auto enumReason = static_cast<NMDeviceStateReason::Enum>(reason);
auto enumNewState = static_cast<NMDeviceState::Enum>(newState);
@@ -57,6 +84,22 @@ void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 re
this->bStateReason = enumReason;
}
void NMDevice::bindNetwork(NMNetwork* net) {
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){
emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path()));
});
QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){
emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject));
});
QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) {
if (visible) emit this->networkAdded(net->frontend());
else emit this->networkRemoved(net->frontend());
});
if (net->visible()) emit this->networkAdded(net->frontend());
}
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
const QString stringPath = path.path();
@@ -160,4 +203,9 @@ DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
}
DBusResult<qs::network::NMDeviceInterfaceFlags::Enum>
DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceInterfaceFlags::Enum>(wire));
}
} // namespace qs::dbus
+25 -2
View File
@@ -8,9 +8,11 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../enums.hpp"
#include "../device.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@@ -22,6 +24,13 @@ struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
static DBusResult<Data> fromWire(Wire wire);
};
template <>
struct DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum> {
using Wire = quint32;
using Data = qs::network::NMDeviceInterfaceFlags::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
@@ -44,16 +53,23 @@ public:
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }
[[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; }
[[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; }
[[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const {
return this->bInterfaceFlags;
}
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }
[[nodiscard]] virtual NetworkDevice* frontend() = 0;
signals:
void loaded();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& apPath
const QDBusObjectPath& specificObjectPath
);
void networkAdded(Network* net);
void networkRemoved(Network* net);
void settingsLoaded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
@@ -66,12 +82,17 @@ signals:
void stateReasonChanged(NMDeviceStateReason::Enum reason);
void lastFailReasonChanged(NMDeviceStateReason::Enum reason);
void autoconnectChanged(bool autoconnect);
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
public slots:
void disconnect();
void setAutoconnect(bool autoconnect);
void setManaged(bool managed);
protected:
void bindFrontend(NetworkDevice* frontend);
void bindNetwork(NMNetwork* net);
private slots:
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
@@ -93,6 +114,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableSettingsPathsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
@@ -102,6 +124,7 @@ private:
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags");
// clang-format on
DBusNMDeviceProxy* deviceProxy = nullptr;
+17
View File
@@ -183,6 +183,23 @@ public:
Q_ENUM(Enum);
};
// Flags for a network interface.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags.
class NMDeviceInterfaceFlags: public QObject {
Q_OBJECT;
public:
enum Enum : quint32 {
None = 0x0,
Up = 0x1,
LowerUp = 0x2,
Promisc = 0x4,
Carrier = 0x10000,
LldpClientEnabled = 0x20000,
};
Q_ENUM(Enum);
};
// 802.11 specific device encryption and authentication capabilities.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
class NMWirelessCapabilities: public QObject {
+310
View File
@@ -0,0 +1,310 @@
#include "network.hpp"
#include <utility>
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qpointer.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "active_connection.hpp"
#include "enums.hpp"
#include "settings.hpp"
#include "utils.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMNetwork::NMNetwork(QObject* parent)
: QObject(parent)
, bKnown(false)
, bReason(NMConnectionStateReason::None)
, bState(NMConnectionState::Deactivated) {}
void NMNetwork::updateReferenceSettings() {
// If the network has no connections, the reference is nullptr.
if (this->mSettings.isEmpty()) {
this->bReferenceSettings = nullptr;
return;
};
// If the network has an active connection, use its settings as the reference.
if (this->mActiveConnection) {
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
if (settings && settings != this->bReferenceSettings) {
this->bReferenceSettings = settings;
}
return;
}
// Otherwise, choose the settings responsible for the last successful connection.
NMSettings* selectedSettings = nullptr;
quint64 selectedTimestamp = 0;
for (auto* settings: this->mSettings.values()) {
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
if (!selectedSettings || timestamp > selectedTimestamp) {
selectedSettings = settings;
selectedTimestamp = timestamp;
}
}
if (this->bReferenceSettings != selectedSettings) {
this->bReferenceSettings = selectedSettings;
}
}
void NMNetwork::addSettings(NMSettings* settings) {
if (this->mSettings.contains(settings->path())) return;
this->mSettings.insert(settings->path(), settings);
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
// Deletes `this`
emit this->settingsRemoved(settings);
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
this->bKnown = true;
this->updateReferenceSettings();
emit this->settingsAdded(settings);
};
void NMNetwork::addActiveConnection(NMActiveConnection* active) {
if (this->mActiveConnection) return;
this->mActiveConnection = active;
this->bState.setBinding([active]() { return active->state(); });
this->bReason.setBinding([active]() { return active->stateReason(); });
auto onDestroyed = [this, active]() {
if (this->mActiveConnection && this->mActiveConnection == active) {
this->mActiveConnection = nullptr;
this->updateReferenceSettings();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceSettings();
};
void NMNetwork::forget() {
if (this->mSettings.isEmpty()) return;
for (auto* conn: this->mSettings.values()) {
conn->forget();
}
}
void NMNetwork::bindFrontend(Network* frontend) {
auto translateState = [this]() { return this->state() == NMConnectionState::Activated; };
frontend->bindableConnected().setBinding(translateState);
frontend->bindableKnown().setBinding([this]() { return this->known(); });
frontend->bindableState().setBinding([this]() {
return static_cast<ConnectionState::Enum>(this->state());
});
frontend->bindableStateChanging().setBinding([this]() {
auto s = static_cast<ConnectionState::Enum>(this->state());
return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting;
});
QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() {
if (this->reason() == NMConnectionStateReason::DeviceDisconnected) {
auto deviceReason = this->deviceFailReason();
if (deviceReason == NMDeviceStateReason::NoSecrets)
emit frontend->connectionFailed(ConnectionFailReason::NoSecrets);
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed);
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
if (deviceReason == NMDeviceStateReason::SsidNotFound)
emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost);
}
});
QObject::connect(
frontend,
&Network::requestConnectWithSettings,
this,
[this](NMSettings* settings) {
if (settings) {
emit this->requestActivateConnection(settings->path());
return;
}
qCInfo(
logNetworkManager
) << "Failed to connectWithSettings: The provided settings no longer exist.";
}
);
// clang-format off
QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget);
QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect);
QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded);
QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved);
// clang-format on
}
NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent)
: NMNetwork(parent)
, mFrontend(new Network(std::move(name), device, this)) {
// Regiter and bind the frontend Network.
this->bindFrontend();
}
void NMGenericNetwork::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMNetwork::bindFrontend(frontend);
QObject::connect(frontend, &Network::requestConnect, this, [this]() {
if (auto* settingsRef = this->referenceSettings()) {
emit this->requestActivateConnection(settingsRef->path());
return;
}
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
return;
});
}
NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent)
: NMNetwork(parent)
, mSsid(ssid)
, 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) {
this->bSecurity.setBinding([ap]() { return ap->security(); });
} else {
this->bSecurity = WifiSecurityType::Unknown;
}
return;
};
auto checkDisappeared = [this]() {
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
};
QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity);
QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity);
QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared);
QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared);
// Register and bind the frontend WifiNetwork.
this->mFrontend = new WifiNetwork(ssid, device, this);
this->bindFrontend();
}
void NMWirelessNetwork::updateReferenceAp() {
// If the network has no APs, the reference is a nullptr.
if (this->mAccessPoints.isEmpty()) {
this->bReferenceAp = nullptr;
this->bSignalStrength = 0;
return;
}
// Otherwise, choose the AP with the strongest signal.
NMAccessPoint* selectedAp = nullptr;
for (auto* ap: this->mAccessPoints.values()) {
// Always prefer the active AP.
if (ap->path() == this->bActiveApPath) {
selectedAp = ap;
break;
}
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
selectedAp = ap;
}
}
if (this->bReferenceAp != selectedAp) {
this->bReferenceAp = selectedAp;
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
}
}
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
if (this->mAccessPoints.contains(ap->path())) return;
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
this->updateReferenceAp();
// Deletes `this`
emit this->apRemoved(ap);
}
};
// clang-format off
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
// clang-format on
this->updateReferenceAp();
};
void NMWirelessNetwork::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMNetwork::bindFrontend(frontend);
auto translateSignal = [this]() { return this->signalStrength() / 100.0; };
frontend->bindableSignalStrength().setBinding(translateSignal);
frontend->bindableSecurity().setBinding([this]() { return this->security(); });
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() {
if (auto* settingsRef = this->referenceSettings()) {
emit this->requestActivateConnection(settingsRef->path());
return;
}
if (auto* apRef = this->referenceAp()) {
emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path());
return;
}
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
return;
});
QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) {
NMSettingsMap settings;
settings["802-11-wireless-security"]["psk"] = psk;
if (const QPointer<NMSettings> ref = this->referenceSettings()) {
auto* call = ref->updateSettings(settings);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this, ref](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message();
} else {
if (!ref) {
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared.";
} else {
emit this->requestActivateConnection(ref->path());
}
}
delete call;
}
);
return;
}
if (auto* apRef = this->referenceAp()) {
emit this->requestAddAndActivateConnection(settings, apRef->path());
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared.";
});
}
} // namespace qs::network
+138
View File
@@ -0,0 +1,138 @@
#pragma once
#include <qobject.h>
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "active_connection.hpp"
#include "enums.hpp"
#include "settings.hpp"
namespace qs::network {
// NMNetwork aggregates NMActiveConnections and NMSettings of the same network.
class NMNetwork: public QObject {
Q_OBJECT;
public:
explicit NMNetwork(QObject* parent = nullptr);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
void connect(const QString& devPath);
void connectWithSettings(const QString& devPath, NMSettings* settings);
// clang-format off
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; }
[[nodiscard]] virtual Network* frontend() = 0;
QBindable<bool> bindableVisible() { return &this->bVisible; }
[[nodiscard]] bool visible() const { return this->bVisible; }
// clang-format on
signals:
void requestDisconnect();
void requestActivateConnection(const QString& settingsPath);
void
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void referenceSettingsChanged(NMSettings* settings);
void visibilityChanged(bool visible);
protected:
void bindFrontend(Network* frontend);
QHash<QString, NMSettings*> mSettings;
Q_OBJECT_BINDABLE_PROPERTY(
NMNetwork,
NMSettings*,
bReferenceSettings,
&NMNetwork::referenceSettingsChanged
);
private:
void updateReferenceSettings();
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged);
// clang-format on
};
// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network.
// This is useful for devices with one network that don't need to extend the base Network class.
class NMGenericNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr);
[[nodiscard]] Network* frontend() override { return this->mFrontend; }
private:
void bindFrontend();
Network* mFrontend;
};
// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality.
class NMWirelessNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
[[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; };
// clang-format on
signals:
void disappeared();
void signalStrengthChanged(quint8 signal);
void securityChanged(WifiSecurityType::Enum security);
void activeApPathChanged(QString path);
void referenceApChanged(NMAccessPoint* ap);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void apRemoved(NMAccessPoint* ap);
private:
void updateReferenceAp();
void bindFrontend();
WifiNetwork* mFrontend;
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged);
// clang-format on
};
} // namespace qs::network
@@ -0,0 +1,4 @@
<node>
<interface name="org.freedesktop.NetworkManager.Device.Wired">
</interface>
</node>
+1
View File
@@ -37,6 +37,7 @@ NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
qDBusRegisterMetaType<QList<NMIPv6Address>>();
qDBusRegisterMetaType<NMIPv6Route>();
qDBusRegisterMetaType<QList<NMIPv6Route>>();
qDBusRegisterMetaType<QMap<QString, QString>>();
this->proxy = new DBusNMConnectionSettingsProxy(
"org.freedesktop.NetworkManager",
+26 -2
View File
@@ -273,6 +273,7 @@ void manualSettingDemarshall(NMSettingsMap& map) {
if (signature == "aa{sv}") return QVariant::fromValue(qdbus_cast<QList<QVariantMap>>(arg));
if (signature == "a(ayuay)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Address>>(arg));
if (signature == "a(ayuayu)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Route>>(arg));
if (signature == "a{ss}") return QVariant::fromValue(qdbus_cast<QMap<QString, QString>>(arg));
return value;
};
@@ -291,7 +292,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
if (s == "802-1x.ca-cert" || s == "802-1x.client-cert" || s == "802-1x.private-key"
|| s == "802-1x.password-raw" || s == "802-1x.phase2-ca-cert"
|| s == "802-1x.phase2-client-cert" || s == "802-1x.phase2-private-key"
|| s == "802-11-wireless.ssid")
|| s == "802-11-wireless.ssid" || s == "802-3-ethernet.cloned-mac-address"
|| s == "802-3-ethernet.mac-address")
{
if (value.typeId() == QMetaType::QString) {
return value.toString().toUtf8();
@@ -423,7 +425,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
// QVariantList -> QStringList
if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search"
|| s == "802-11-wireless.seen-bssids")
|| s == "802-11-wireless.seen-bssids" || s == "802-3-ethernet.mac-address-blacklist"
|| s == "802-3-ethernet.mac-address-denylist" || s == "802-3-ethernet.s390-subchannels")
{
if (value.typeId() == QMetaType::QVariantList) {
QStringList stringList;
@@ -435,6 +438,18 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
return QVariant();
}
// QVariantMap -> QMap<QString, QString>
if (s == "802-3-ethernet.s390-options") {
if (value.canConvert<QVariantMap>()) {
QMap<QString, QString> r;
for (const auto& [key, val]: value.toMap().asKeyValueRange()) {
r.insert(key, val.toString());
}
return QVariant::fromValue(r);
}
return QVariant();
}
// double (whole number) -> qint32
if (value.typeId() == QMetaType::Double) {
auto num = value.toDouble();
@@ -498,6 +513,15 @@ QVariant settingTypeToQml(const QVariant& value) {
return out;
}
// QMap<QString, QString> -> QVariantMap
if (value.userType() == qMetaTypeId<QMap<QString, QString>>()) {
QVariantMap out;
for (const auto& [key, val]: value.value<QMap<QString, QString>>().asKeyValueRange()) {
out.insert(key, val);
}
return out;
}
return value;
}
+101
View File
@@ -0,0 +1,101 @@
#include "wired.hpp"
#include <qdbusconnection.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWiredDevice::NMWiredDevice(const QString& path, QObject* parent): NMDevice(path, parent) {
this->wiredProxy = new DBusNMWiredProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->wiredProxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for wired device at" << path;
return;
}
// Wait to create the NMGenericNetwork/Network
// until the dbus properties load because the network requires the interface name.
QObject::connect(
&this->wiredProperties,
&DBusPropertyGroup::getAllFinished,
this,
&NMWiredDevice::initWired,
Qt::SingleShotConnection
);
this->wiredProperties.setInterface(this->wiredProxy);
this->wiredProperties.updateAllViaGetAll();
// Register and bind the frontend WiredDevice.
this->mFrontend = new WiredDevice(this);
this->bindFrontend();
};
void NMWiredDevice::initWired() {
// Register the NMGenericNetwork and bind the frontend Network.
// For wired networking, there is only one Network and it should exist for the lifetime of the device.
auto* net = new NMGenericNetwork(QString(), this->frontend(), this);
net->frontend()->bindableName().setBinding([this]() { return this->interface(); });
auto visible = [this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
};
net->bindableVisible().setBinding(visible);
this->NMDevice::bindNetwork(net);
this->mNetwork = net;
// clang-format off
QObject::connect(this, &NMWiredDevice::settingsLoaded, this, &NMWiredDevice::onSettingsLoaded);
QObject::connect(this, &NMWiredDevice::activeConnectionLoaded, this, &NMWiredDevice::onActiveConnectionLoaded);
// clang-format on
emit this->loaded();
}
void NMWiredDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
frontend->bindableLinkSpeed().setBinding([this]() { return this->bSpeed.value(); });
frontend->bindableHasLink().setBinding([this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
});
}
void NMWiredDevice::onSettingsLoaded(NMSettings* settings) {
this->mNetwork->addSettings(settings);
}
void NMWiredDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
this->mNetwork->addActiveConnection(active);
}
bool NMWiredDevice::isValid() const {
return this->NMDevice::isValid() && (this->wiredProxy && this->wiredProxy->isValid());
}
} // namespace qs::network
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <qdbusextratypes.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wired interface
// Owns the lifetime of a NMGenericNetwork, and a frontend WiredDevice.
class NMWiredDevice: public NMDevice {
Q_OBJECT;
public:
explicit NMWiredDevice(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const override;
[[nodiscard]] WiredDevice* frontend() override { return this->mFrontend; };
signals:
void speedChanged(quint32 speed);
private slots:
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
private:
void initWired();
void bindFrontend();
WiredDevice* mFrontend = nullptr;
NMGenericNetwork* mNetwork = nullptr;
Q_OBJECT_BINDABLE_PROPERTY(NMWiredDevice, quint32, bSpeed, &NMWiredDevice::speedChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wiredProperties);
QS_DBUS_PROPERTY_BINDING(NMWiredDevice, pSpeed, bSpeed, wiredProperties, "Speed");
DBusNMWiredProxy* wiredProxy = nullptr;
};
}; // namespace qs::network
+28 -291
View File
@@ -1,5 +1,4 @@
#include "wireless.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qdatetime.h>
@@ -12,7 +11,6 @@
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpointer.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -28,6 +26,7 @@
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
#include "utils.hpp"
@@ -38,143 +37,6 @@ namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent)
: QObject(parent)
, mSsid(std::move(ssid))
, bKnown(false)
, bSecurity(WifiSecurityType::Unknown)
, bReason(NMConnectionStateReason::None)
, bState(NMConnectionState::Deactivated) {}
void NMWirelessNetwork::updateReferenceSettings() {
// If the network has no connections, the reference is nullptr.
if (this->mSettings.isEmpty()) {
this->mReferenceSettings = nullptr;
this->bSecurity = WifiSecurityType::Unknown;
if (this->mReferenceAp) {
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
}
return;
};
// If the network has an active connection, use its settings as the reference.
if (this->mActiveConnection) {
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
if (settings && settings != this->mReferenceSettings) {
this->mReferenceSettings = settings;
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
}
return;
}
// Otherwise, choose the settings responsible for the last successful connection.
NMSettings* selectedSettings = nullptr;
quint64 selectedTimestamp = 0;
for (auto* settings: this->mSettings.values()) {
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
if (!selectedSettings || timestamp > selectedTimestamp) {
selectedSettings = settings;
selectedTimestamp = timestamp;
}
}
if (this->mReferenceSettings != selectedSettings) {
this->mReferenceSettings = selectedSettings;
this->bSecurity.setBinding([selectedSettings]() {
return securityFromSettingsMap(selectedSettings->map());
});
}
}
void NMWirelessNetwork::updateReferenceAp() {
// If the network has no APs, the reference is a nullptr.
if (this->mAccessPoints.isEmpty()) {
this->mReferenceAp = nullptr;
this->bSignalStrength = 0;
return;
}
// Otherwise, choose the AP with the strongest signal.
NMAccessPoint* selectedAp = nullptr;
for (auto* ap: this->mAccessPoints.values()) {
// Always prefer the active AP.
if (ap->path() == this->bActiveApPath) {
selectedAp = ap;
break;
}
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
selectedAp = ap;
}
}
if (this->mReferenceAp != selectedAp) {
this->mReferenceAp = selectedAp;
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
// Reference AP is used for security when there's no connection settings.
if (!this->mReferenceSettings) {
this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); });
}
}
}
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
if (this->mAccessPoints.contains(ap->path())) return;
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
this->updateReferenceAp();
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
// clang-format off
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
// clang-format on
this->updateReferenceAp();
};
void NMWirelessNetwork::addSettings(NMSettings* settings) {
if (this->mSettings.contains(settings->path())) return;
this->mSettings.insert(settings->path(), settings);
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
this->bKnown = true;
this->updateReferenceSettings();
emit this->settingsAdded(settings);
};
void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) {
if (this->mActiveConnection) return;
this->mActiveConnection = active;
this->bState.setBinding([active]() { return active->state(); });
this->bReason.setBinding([active]() { return active->stateReason(); });
auto onDestroyed = [this, active]() {
if (this->mActiveConnection && this->mActiveConnection == active) {
this->mActiveConnection = nullptr;
this->updateReferenceSettings();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceSettings();
};
void NMWirelessNetwork::forget() {
if (this->mSettings.isEmpty()) return;
for (auto* conn: this->mSettings.values()) {
conn->forget();
}
}
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
: NMDevice(path, parent)
, mScanTimer(this) {
@@ -203,6 +65,10 @@ NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
this->wirelessProperties.setInterface(this->wirelessProxy);
this->wirelessProperties.updateAllViaGetAll();
// Register and bind the frontend WifiDevice.
this->mFrontend = new WifiDevice(this);
this->bindFrontend();
}
void NMWirelessDevice::initWireless() {
@@ -214,7 +80,9 @@ void NMWirelessDevice::initWireless() {
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged);
// clang-format on
this->registerAccessPoints();
emit this->loaded();
}
void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) {
@@ -271,7 +139,7 @@ void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
}
void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
// Find an exisiting network with connection settings that matches the active
// Find an existing network with connection settings that matches the active
const QString activeConnPath = active->connection().path();
for (const auto& net: this->mNetworks.values()) {
for (auto* settings: net->settings()) {
@@ -364,176 +232,45 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
}
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
auto* net = new NMWirelessNetwork(ssid, this);
auto* net = new NMWirelessNetwork(ssid, this->frontend(), this);
this->NMDevice::bindNetwork(net);
auto visible = [this, net]() {
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
};
net->bindableVisible().setBinding(visible);
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
this->mNetworks.insert(ssid, net);
this->registerFrontendNetwork(net);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
return net;
}
void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) {
auto ssid = net->ssid();
auto* frontendNet = new WifiNetwork(ssid, net);
// Bind WifiNetwork to NMWirelessNetwork
auto translateSignal = [net]() { return net->signalStrength() / 100.0; };
auto translateState = [net]() { return net->state() == NMConnectionState::Activated; };
frontendNet->bindableSignalStrength().setBinding(translateSignal);
frontendNet->bindableConnected().setBinding(translateState);
frontendNet->bindableKnown().setBinding([net]() { return net->known(); });
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
frontendNet->bindableState().setBinding([net]() {
return static_cast<ConnectionState::Enum>(net->state());
});
QObject::connect(net, &NMWirelessNetwork::reasonChanged, this, [net, frontendNet]() {
if (net->reason() == NMConnectionStateReason::DeviceDisconnected) {
auto deviceReason = net->deviceFailReason();
if (deviceReason == NMDeviceStateReason::NoSecrets)
emit frontendNet->connectionFailed(ConnectionFailReason::NoSecrets);
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientFailed);
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
if (deviceReason == NMDeviceStateReason::SsidNotFound)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiNetworkLost);
}
});
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
if (net->referenceSettings()) {
emit this->activateConnection(
QDBusObjectPath(net->referenceSettings()->path()),
QDBusObjectPath(this->path())
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
NMSettingsMap(),
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connect to"
<< this->path() + ": The network disappeared.";
});
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithPsk,
this,
[this, net](const QString& psk) {
NMSettingsMap settings;
settings["802-11-wireless-security"]["psk"] = psk;
if (const QPointer<NMSettings> ref = net->referenceSettings()) {
auto* call = ref->updateSettings(settings);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this, ref](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager)
<< "Failed to write PSK for" << this->path() + ":" << reply.error().message();
} else {
if (!ref) {
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The settings disappeared.";
} else {
emit this->activateConnection(
QDBusObjectPath(ref->path()),
QDBusObjectPath(this->path())
);
}
}
delete call;
}
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
settings,
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The network disappeared.";
}
);
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithSettings,
this,
[this](NMSettings* settings) {
if (settings) {
emit this->activateConnection(
QDBusObjectPath(settings->path()),
QDBusObjectPath(this->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithSettings to"
<< this->path() + ": The provided settings no longer exist.";
}
);
QObject::connect(
net,
&NMWirelessNetwork::visibilityChanged,
this,
[this, frontendNet](bool visible) {
if (visible) this->networkAdded(frontendNet);
else this->networkRemoved(frontendNet);
}
);
// clang-format off
QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect);
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
QObject::connect(net, &NMWirelessNetwork::settingsAdded, frontendNet, &WifiNetwork::settingsAdded);
QObject::connect(net, &NMWirelessNetwork::settingsRemoved, frontendNet, &WifiNetwork::settingsRemoved);
// clang-format on
this->mFrontendNetworks.insert(ssid, frontendNet);
if (net->visible()) emit this->networkAdded(frontendNet);
}
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
if (frontendNet) {
if (net->visible()) emit this->networkRemoved(frontendNet);
frontendNet->deleteLater();
}
}
void NMWirelessDevice::removeNetwork() {
auto* net = qobject_cast<NMWirelessNetwork*>(this->sender());
if (this->mNetworks.take(net->ssid())) {
this->removeFrontendNetwork(net);
if (net->visible()) emit this->networkRemoved(net->frontend());
delete net;
};
}
void NMWirelessDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
auto translateMode = [this]() {
switch (this->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
frontend->bindableMode().setBinding(translateMode);
this->bindableScanning().setBinding([frontend]() { return frontend->scannerEnabled(); });
}
bool NMWirelessDevice::isValid() const {
return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid());
}
+5 -73
View File
@@ -13,6 +13,7 @@
#include "dbus_nm_wireless.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@@ -33,76 +34,9 @@ struct DBusDataTransform<QDateTime> {
} // namespace qs::dbus
namespace qs::network {
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMSettings objects.
class NMWirelessNetwork: public QObject {
Q_OBJECT;
public:
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->mReferenceSettings; }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
QBindable<bool> bindableVisible() { return &this->bVisible; }
bool visible() const { return this->bVisible; }
// clang-format on
signals:
void disappeared();
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void visibilityChanged(bool visible);
void signalStrengthChanged(quint8 signal);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void securityChanged(WifiSecurityType::Enum security);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void activeApPathChanged(QString path);
private:
void updateReferenceAp();
void updateReferenceSettings();
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMSettings*> mSettings;
NMAccessPoint* mReferenceAp = nullptr;
NMSettings* mReferenceSettings = nullptr;
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMWirelessNetwork::deviceFailReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
// clang-format on
};
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s).
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), and a frontend WifiDevice.
class NMWirelessDevice: public NMDevice {
Q_OBJECT;
@@ -114,12 +48,11 @@ public:
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; }
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; }
[[nodiscard]] WifiDevice* frontend() override { return this->mFrontend; };
signals:
void accessPointLoaded(NMAccessPoint* ap);
void accessPointRemoved(NMAccessPoint* ap);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
void lastScanChanged(QDateTime lastScan);
void scanningChanged(bool scanning);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
@@ -137,17 +70,16 @@ private slots:
private:
void registerAccessPoint(const QString& path);
void registerFrontendNetwork(NMWirelessNetwork* net);
void removeFrontendNetwork(NMWirelessNetwork* net);
void removeNetwork();
bool checkVisibility(WifiNetwork* net);
void registerAccessPoints();
void initWireless();
void bindFrontend();
NMWirelessNetwork* registerNetwork(const QString& ssid);
WifiDevice* mFrontend;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMWirelessNetwork*> mNetworks;
QHash<QString, WifiNetwork*> mFrontendNetworks;
QDateTime mLastScanRequest;
QTimer mScanTimer;
+82
View File
@@ -0,0 +1,82 @@
#include "qml.hpp"
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
} // namespace qs::network
+151
View File
@@ -0,0 +1,151 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
namespace qs::network {
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
} // namespace qs::network
+240 -213
View File
@@ -73,6 +73,241 @@ Scope {
}
}
Component {
id: deviceDelegate
WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
RowLayout {
visible: modelData.type === DeviceType.Wired
CheckBox {
text: "Link connected"
checked: modelData.hasLink
enabled: false
}
Label {
text: `Link max speed: ${modelData.linkSpeed} Mbps`
}
}
RowLayout {
visible: modelData.type === DeviceType.Wifi
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
}
Repeater {
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
if (modelData.device?.type === DeviceType.Wifi) {
return b.signalStrength - a.signalStrength;
}
})
}
WrapperRectangle {
id: ethernetNetwork
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
visible: modelData.device?.type === DeviceType.Wifi
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (ethernetNetwork.chosenSettings)
modelData.connectWithSettings(ethernetNetwork.chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (ethernetNetwork.chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
}
}
}
FloatingWindow {
color: contentItem.palette.window
@@ -139,219 +374,11 @@ Scope {
Layout.fillWidth: true
Layout.fillHeight: true
model: Networking.devices
delegate: WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
Repeater {
Layout.fillWidth: true
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
return b.signalStrength - a.signalStrength;
})
}
WrapperRectangle {
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (chosenSettings)
modelData.connectWithSettings(chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
delegate: Component {
Loader {
required property var modelData
width: ListView.view.width
sourceComponent: deviceDelegate
}
}
}
+2 -4
View File
@@ -19,7 +19,8 @@ namespace {
QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg);
}
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent)
: Network(std::move(ssid), device, parent) {};
void WifiNetwork::connectWithPsk(const QString& psk) {
if (this->bConnected) {
@@ -42,9 +43,6 @@ void WifiDevice::setScannerEnabled(bool enabled) {
this->bScannerEnabled = enabled;
}
void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); }
void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) {
+2 -11
View File
@@ -7,7 +7,6 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
@@ -27,7 +26,7 @@ class WifiNetwork: public Network {
// clang-format on
public:
explicit WifiNetwork(QString ssid, QObject* parent = nullptr);
explicit WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network with the given PSK. If the PSK is wrong,
/// a @@Network.connectionFailed(s) signal will be emitted with `NoSecrets`.
///
@@ -61,9 +60,6 @@ class WifiDevice: public NetworkDevice {
QML_UNCREATABLE("");
// clang-format off
/// A list of this available or connected wifi networks.
QSDOC_TYPE_OVERRIDE(ObjectModel<WifiNetwork>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// True when currently scanning for networks.
/// When enabled, the scanner populates the device with an active list of available wifi networks.
Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled);
@@ -74,10 +70,6 @@ class WifiDevice: public NetworkDevice {
public:
explicit WifiDevice(QObject* parent = nullptr);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
[[nodiscard]] ObjectModel<WifiNetwork>* networks() { return &this->mNetworks; }
QBindable<bool> bindableScannerEnabled() { return &this->bScannerEnabled; }
[[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; }
void setScannerEnabled(bool enabled);
@@ -88,12 +80,11 @@ signals:
void scannerEnabledChanged(bool enabled);
private:
ObjectModel<WifiNetwork> mNetworks {this};
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged);
};
}; // namespace qs::network
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network);
QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device);
+22
View File
@@ -0,0 +1,22 @@
#include "wired.hpp"
#include <qobject.h>
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
WiredDevice::WiredDevice(QObject* parent): NetworkDevice(DeviceType::Wired, parent) {}
void WiredDevice::networkAdded(Network* net) {
this->NetworkDevice::networkAdded(net);
this->bNetwork = net;
};
void WiredDevice::networkRemoved(Network* net) {
this->NetworkDevice::networkRemoved(net);
this->bNetwork = nullptr;
};
} // namespace qs::network
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "device.hpp"
#include "network.hpp"
namespace qs::network {
///! Wired variant of a @@NetworkDevice.
class WiredDevice: public NetworkDevice {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("");
/// The wired network for this device or `null`.
///
/// > [!NOTE] This network is only available when @@hasLink is `true`.
Q_PROPERTY(Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork);
/// The maximum speed of the physical device link, in megabits per second.
Q_PROPERTY(quint32 linkSpeed READ default NOTIFY linkSpeedChanged BINDABLE bindableLinkSpeed);
/// True if the wired device has a physical link (cable plugged in).
Q_PROPERTY(bool hasLink READ default NOTIFY hasLinkChanged BINDABLE bindableHasLink);
public:
explicit WiredDevice(QObject* parent = nullptr);
void networkAdded(Network* net) override;
void networkRemoved(Network* net) override;
[[nodiscard]] Network* network() const { return this->bNetwork; }
QBindable<Network*> bindableNetwork() { return &this->bNetwork; }
QBindable<quint32> bindableLinkSpeed() { return &this->bLinkSpeed; }
QBindable<bool> bindableHasLink() { return &this->bHasLink; }
signals:
void networkChanged();
void linkSpeedChanged();
void hasLinkChanged();
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, Network*, bNetwork, &WiredDevice::networkChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, quint32, bLinkSpeed, &WiredDevice::linkSpeedChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, bool, bHasLink, &WiredDevice::hasLinkChanged);
// clang-format on
};
} // namespace qs::network
+2 -2
View File
@@ -325,8 +325,8 @@ public:
void setLoopState(MprisLoopState::Enum loopState);
[[nodiscard]] qreal rate() const { return this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bRate; }
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bMinRate; }
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bMaxRate; }
void setRate(qreal rate);
[[nodiscard]] bool shuffle() const { return this->bShuffle; }
+15 -3
View File
@@ -8,6 +8,8 @@
#include <qdbusservicewatcher.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobjectdefs.h>
#include <qqmlengine.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -192,19 +194,29 @@ uint NotificationServer::Notify(
notification->updateProperties(appName, appIcon, summary, body, actions, hints, expireTimeout);
auto id = notification->id();
if (!old) {
emit this->notification(notification);
if (!notification->isTracked()) {
emit this->NotificationClosed(notification->id(), notification->closeReason());
auto closeReason = notification->closeReason();
// Emit NotificationClosed after Notify returns
QMetaObject::invokeMethod(
this,
[this, id, closeReason]() { emit this->NotificationClosed(id, closeReason); },
Qt::QueuedConnection
);
delete notification;
} else {
this->idMap.insert(notification->id(), notification);
this->idMap.insert(id, notification);
this->mNotifications.insertObject(notification);
}
}
return notification->id();
return id;
}
} // namespace qs::service::notifications
+41 -2
View File
@@ -193,7 +193,7 @@ void PwNode::initProps(const spa_dict* props) {
qCCritical(
logNode
) << this
<< "has a device.id property that does not corrospond to a device object. Id:" << id;
<< "has a device.id property that does not correspond to a device object. Id:" << id;
}
}
}
@@ -580,7 +580,7 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
spa_pod* iter = nullptr;
SPA_POD_ARRAY_FOREACH(volumes, iter) {
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
// Cubing behavior found in MPD source, and appears to correspond to everyone else's measurements correctly.
auto linear = *reinterpret_cast<float*>(iter);
auto visual = std::cbrt(linear);
props.volumes.push_back(visual);
@@ -595,6 +595,45 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
}
}
if (props.channels.isEmpty()) {
// See spa/param/audio/layout.h and pw utils.
using C = PwAudioChannel;
// clang-format off
switch (props.volumes.length()) {
case 1: props.channels = {C::Mono}; break;
case 2: props.channels = {C::FrontLeft, C::FrontRight}; break;
case 3: props.channels = {C::FrontLeft, C::FrontRight, C::LowFrequencyEffects}; break;
case 4: props.channels = {C::FrontLeft, C::FrontRight, C::RearLeft, C::RearRight}; break;
case 5:
props.channels = {C::FrontLeft, C::FrontRight, C::FrontCenter, C::SideLeft, C::SideRight};
break;
case 6:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::LowFrequencyEffects,
C::SideLeft, C::SideRight
};
break;
case 7:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::RearLeft, C::RearRight,
C::SideLeft, C::SideRight
};
break;
case 8:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::LowFrequencyEffects,
C::RearLeft, C::RearRight,
C::SideLeft, C::SideRight
};
break;
default: break;
}
// clang-format on
}
if (muteProp) {
spa_pod_get_bool(&muteProp->value, &props.mute);
}
+1 -1
View File
@@ -140,7 +140,7 @@ public:
// This is equivalent to the media class `Video/Source` and is composed
// of the @@PwNodeType.Video and @@PwNodeType.Source flags.
VideoSource = Video | Source,
// A consumer of video, such as a program that is recieving a video stream.
// A consumer of video, such as a program that is receiving a video stream.
//
// This is equivalent to the media class `Video/Sink` and is composed of the
// @@PwNodeType.Video and @@PwNodeType.Sink flags.
+23 -2
View File
@@ -245,13 +245,14 @@ void PwPeakStream::handleProcess() {
if (!this->formatReady || this->stream == nullptr) return;
auto* buffer = pw_stream_dequeue_buffer(this->stream);
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
if (buffer == nullptr) {
qCWarning(logPeak) << "Peak monitor ran out of buffers.";
return;
}
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
auto* spaBuffer = buffer->buffer;
if (spaBuffer == nullptr || spaBuffer->n_datas < 1) {
return;
@@ -277,7 +278,27 @@ void PwPeakStream::handleProcess() {
QVector<float> volumes;
if (auto* audioData = dynamic_cast<PwNodeBoundAudio*>(this->node->boundData)) {
if (!this->node->shouldUseDevice()) volumes = audioData->volumes();
// Device volumes don't require inverse scaling
if (!this->node->shouldUseDevice()) {
const auto& nchannels = audioData->channels();
const auto& nvolumes = audioData->volumes();
for (const auto channel: this->monitor->mChannels) {
for (auto i = 0; i != nchannels.length(); i++) {
if (nchannels[i] == channel) {
volumes.push_back(nvolumes[i]);
break;
}
}
}
if (volumes.length() != channelCount) {
qCCritical(logPeak) << this->node
<< "is missing channels present in capture stream. Node channels:"
<< nchannels << "Stream channels:" << this->monitor->mChannels;
return;
}
}
}
this->channelPeaks.fill(0.0f, channelCount);
+4 -4
View File
@@ -233,7 +233,7 @@ class PwNodeAudioIface: public QObject {
///
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
Q_PROPERTY(QVector<qs::service::pipewire::PwAudioChannel::Enum> channels READ channels NOTIFY channelsChanged);
/// The volumes of each audio channel individually. Each entry corrosponds to
/// The volumes of each audio channel individually. Each entry corresponds to
/// the volume of the channel at the same index in @@channels. @@volumes and @@channels
/// will always be the same length.
///
@@ -274,13 +274,13 @@ class PwNodeIface: public PwObjectIface {
/// Mainly useful for debugging. You can inspect the node directly
/// with `pw-cli i <id>`.
Q_PROPERTY(quint32 id READ id CONSTANT);
/// The node's name, corrosponding to the object's `node.name` property.
/// The node's name, corresponding to the object's `node.name` property.
Q_PROPERTY(QString name READ name CONSTANT);
/// The node's description, corrosponding to the object's `node.description` property.
/// The node's description, corresponding to the object's `node.description` property.
///
/// May be empty. Generally more human readable than @@name.
Q_PROPERTY(QString description READ description CONSTANT);
/// The node's nickname, corrosponding to the object's `node.nickname` property.
/// The node's nickname, corresponding to the object's `node.nickname` property.
///
/// May be empty. Generally but not always more human readable than @@description.
Q_PROPERTY(QString nickname READ nickname CONSTANT);
+2 -2
View File
@@ -127,14 +127,14 @@ signals:
///
/// This may be because the user entered the wrong password or otherwise
/// failed to authenticate.
/// This signal is not emmitted when the user canceled the request or it
/// This signal is not emitted when the user canceled the request or it
/// was cancelled by the PolKit daemon.
///
/// After this signal, a new session is automatically started for the same
/// identity.
void authenticationFailed();
/// Emmitted when on ongoing authentication request is cancelled by the PolKit daemon.
/// Emitted when an ongoing authentication request is cancelled by the PolKit daemon.
void authenticationRequestCancelled();
void selectedIdentityChanged();
+1 -1
View File
@@ -41,7 +41,7 @@ A conversation can take multiple turns, for example if second factors are involv
If authentication fails, we automatically create a fresh session so the user can try again.
The @@Quickshell.Services.Polkit.AuthFlow.authenticationFailed signal is emitted in this case.
If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeeded signal. At this point, the dialog can be closed.
If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeded signal. At this point, the dialog can be closed.
If additional requests are queued, you will receive the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal again.
#### Cancelled requests
+1 -1
View File
@@ -195,7 +195,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniIconPixmapList, bAttentionIconPixmaps);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bAttentionMovieName);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniTooltip, bTooltip);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu, &StatusNotifierItem::onlyMenuChanged);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QDBusObjectPath, bMenuPath);
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bHasMenu, &StatusNotifierItem::hasMenuChanged);
+1 -1
View File
@@ -105,7 +105,7 @@ if (WAYLAND_SESSION_LOCK)
endif()
if (WAYLAND_TOPLEVEL_MANAGEMENT)
add_subdirectory(toplevel_management)
add_subdirectory(toplevel)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement)
endif()
+1 -2
View File
@@ -799,7 +799,6 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
if (plane.fd < 0) {
qCritical(logDmabuf) << "Failed to get gbm_bo fd for plane" << i << qt_error_string(plane.fd);
params.destroy();
gbm_bo_destroy(bo);
return nullptr;
}
@@ -1121,7 +1120,7 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co
VkDeviceMemory memory = VK_NULL_HANDLE;
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
// takes ownership of the fd on success. Without dup, WlDmaBuffer would double-close.
const int dupFd =
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
if (dupFd < 0) {
+3 -1
View File
@@ -78,7 +78,9 @@ void WlBufferManager::initWindow(QQuickWindow* window) {
auto success = this->p->dmabuf.initRenderFormats(window);
if (!success) {
qCWarning(logBuffer) << "Render format initialization failed. All buffers will fall back to SHM.";
qCWarning(
logBuffer
) << "Render format initialization failed. All buffers will fall back to SHM.";
this->p->mRenderFormatsFailed = true;
}
+20 -8
View File
@@ -24,7 +24,7 @@
#include "../../../core/logcat.hpp"
#include "../../../core/model.hpp"
#include "../../../core/qmlscreen.hpp"
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "hyprland_toplevel.hpp"
#include "monitor.hpp"
#include "toplevel_mapping.hpp"
@@ -74,10 +74,23 @@ HyprlandIpc::HyprlandIpc() {
// clang-format on
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
this->refreshMonitors(true);
this->refreshWorkspaces(true);
this->refreshToplevels();
this->makeRequest("j/status", [&, this](bool success, QByteArray resp) {
if (success) {
this->bUsingLua = [&]() {
if (resp == "unknown request") return false;
auto json = QJsonDocument::fromJson(resp).object();
auto provider = json.value("configProvider");
return provider == "lua";
}();
} else {
qCWarning(logHyprlandIpc) << "Hyprland ipc status request failed.";
}
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
this->refreshMonitors(true);
this->refreshWorkspaces(true);
this->refreshToplevels();
});
}
QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; }
@@ -126,11 +139,10 @@ void HyprlandIpc::eventSocketReady() {
}
void HyprlandIpc::toplevelAddressed(
wayland::toplevel_management::impl::ToplevelHandle* handle,
wayland::toplevel::wlr::ToplevelHandle* handle,
quint64 address
) {
auto* waylandToplevel =
wayland::toplevel_management::ToplevelManager::instance()->forImpl(handle);
auto* waylandToplevel = wayland::toplevel::ToplevelManager::instance()->forImpl(handle);
if (!waylandToplevel) return;
+7 -5
View File
@@ -15,7 +15,7 @@
#include "../../../core/model.hpp"
#include "../../../core/qmlscreen.hpp"
#include "../../../core/streamreader.hpp"
#include "../../../wayland/toplevel_management/handle.hpp"
#include "../../../wayland/toplevel/wlr_toplevel.hpp"
namespace qs::hyprland::ipc {
@@ -81,6 +81,8 @@ public:
[[nodiscard]] HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
[[nodiscard]] QBindable<bool> bindableUsingLua() const { return &this->bUsingLua; }
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
}
@@ -116,6 +118,7 @@ signals:
void connected();
void rawEvent(HyprlandIpcEvent* event);
void usingLuaChanged();
void focusedMonitorChanged();
void focusedWorkspaceChanged();
void activeToplevelChanged();
@@ -125,10 +128,7 @@ private slots:
void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
void eventSocketReady();
void toplevelAddressed(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
quint64 address
);
void toplevelAddressed(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
void onFocusedMonitorDestroyed();
@@ -155,6 +155,8 @@ private:
HyprlandIpcEvent event {this};
Q_OBJECT_BINDABLE_PROPERTY(HyprlandIpc, bool, bUsingLua, &HyprlandIpc::usingLuaChanged);
Q_OBJECT_BINDABLE_PROPERTY(
HyprlandIpc,
HyprlandMonitor*,
@@ -6,12 +6,12 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../toplevel_management/qml.hpp"
#include "../../toplevel/qml.hpp"
#include "connection.hpp"
#include "toplevel_mapping.hpp"
#include "workspace.hpp"
using namespace qs::wayland::toplevel_management;
using namespace qs::wayland::toplevel;
namespace qs::hyprland::ipc {
@@ -112,7 +112,7 @@ Toplevel* HyprlandToplevel::waylandHandle() {
return ToplevelManager::instance()->forImpl(this->mWaylandHandle);
}
void HyprlandToplevel::setWaylandHandle(impl::ToplevelHandle* handle) {
void HyprlandToplevel::setWaylandHandle(wlr::ToplevelHandle* handle) {
if (this->mWaylandHandle == handle) return;
if (this->mWaylandHandle) {
QObject::disconnect(this->mWaylandHandle, nullptr, this, nullptr);
@@ -7,8 +7,8 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel_management/qml.hpp"
#include "../../toplevel/qml.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "connection.hpp"
namespace qs::hyprland::ipc {
@@ -30,7 +30,7 @@ class HyprlandToplevel: public QObject {
/// Will be null until the address is reported
Q_PROPERTY(HyprlandToplevel* handle READ hyprlandHandle NOTIFY hyprlandHandleChanged);
/// The wayland toplevel handle. Will be null intil the address is reported
Q_PROPERTY(qs::wayland::toplevel_management::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
Q_PROPERTY(qs::wayland::toplevel::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
/// The title of the toplevel
Q_PROPERTY(QString title READ default NOTIFY titleChanged BINDABLE bindableTitle);
/// Whether the toplevel is active or not
@@ -53,7 +53,7 @@ public:
/// When invoked from HyprlandIpc, reacting to Hyprland's IPC events.
explicit HyprlandToplevel(HyprlandIpc* ipc);
/// When attached from a Toplevel
explicit HyprlandToplevel(HyprlandIpc* ipc, qs::wayland::toplevel_management::Toplevel* toplevel);
explicit HyprlandToplevel(HyprlandIpc* ipc, qs::wayland::toplevel::Toplevel* toplevel);
static HyprlandToplevel* qmlAttachedProperties(QObject* object);
@@ -69,8 +69,8 @@ public:
[[nodiscard]] HyprlandToplevel* hyprlandHandle() { return this->mHyprlandHandle; }
void setHyprlandHandle(HyprlandToplevel* handle);
[[nodiscard]] wayland::toplevel_management::Toplevel* waylandHandle();
void setWaylandHandle(wayland::toplevel_management::impl::ToplevelHandle* handle);
[[nodiscard]] wayland::toplevel::Toplevel* waylandHandle();
void setWaylandHandle(wayland::toplevel::wlr::ToplevelHandle* handle);
// clang-format on
[[nodiscard]] QBindable<QString> bindableTitle() { return &this->bTitle; }
@@ -105,7 +105,7 @@ private:
quint64 mAddress = 0;
HyprlandIpc* ipc;
qs::wayland::toplevel_management::impl::ToplevelHandle* mWaylandHandle = nullptr;
qs::wayland::toplevel::wlr::ToplevelHandle* mWaylandHandle = nullptr;
HyprlandToplevel* mHyprlandHandle = nullptr;
// clang-format off
+4
View File
@@ -51,6 +51,10 @@ void HyprlandIpcQml::refreshToplevels() { HyprlandIpc::instance()->refreshToplev
QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->requestSocketPath(); }
QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); }
QBindable<bool> HyprlandIpcQml::bindableUsingLua() {
return HyprlandIpc::instance()->bindableUsingLua();
}
QBindable<HyprlandMonitor*> HyprlandIpcQml::bindableFocusedMonitor() {
return HyprlandIpc::instance()->bindableFocusedMonitor();
}
+7 -1
View File
@@ -16,6 +16,10 @@ namespace qs::hyprland::ipc {
class HyprlandIpcQml: public QObject {
Q_OBJECT;
// clang-format off
/// True if Hyprland is running in lua mode. Dispatcher syntax changes when using lua.
///
/// This property will be false until the Hyprland module is initialized.
Q_PROPERTY(bool usingLua READ default NOTIFY usingLuaChanged BINDABLE bindableUsingLua);
/// Path to the request socket (.socket.sock)
Q_PROPERTY(QString requestSocketPath READ requestSocketPath CONSTANT);
/// Path to the event socket (.socket2.sock)
@@ -47,7 +51,7 @@ public:
/// Execute a hyprland [dispatcher](https://wiki.hyprland.org/Configuring/Dispatchers).
Q_INVOKABLE static void dispatch(const QString& request);
/// Get the HyprlandMonitor object that corrosponds to a quickshell screen.
/// Get the HyprlandMonitor object that corresponds to a quickshell screen.
Q_INVOKABLE static HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
/// Refresh monitor information.
@@ -70,6 +74,7 @@ public:
[[nodiscard]] static QString requestSocketPath();
[[nodiscard]] static QString eventSocketPath();
[[nodiscard]] static QBindable<bool> bindableUsingLua();
[[nodiscard]] static QBindable<HyprlandMonitor*> bindableFocusedMonitor();
[[nodiscard]] static QBindable<HyprlandWorkspace*> bindableFocusedWorkspace();
[[nodiscard]] static QBindable<HyprlandToplevel*> bindableActiveToplevel();
@@ -83,6 +88,7 @@ signals:
/// See [Hyprland Wiki: IPC](https://wiki.hyprland.org/IPC/) for a list of events.
void rawEvent(qs::hyprland::ipc::HyprlandIpcEvent* event);
void usingLuaChanged();
void focusedMonitorChanged();
void focusedWorkspaceChanged();
void activeToplevelChanged();
@@ -6,9 +6,9 @@
#include <qtypes.h>
#include <qwaylandclientextension.h>
#include "../../toplevel_management/manager.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
using namespace qs::wayland::toplevel_management::impl;
using namespace qs::wayland::toplevel::wlr;
namespace qs::hyprland::ipc {
+8 -12
View File
@@ -8,7 +8,7 @@
#include <qwayland-hyprland-toplevel-mapping-v1.h>
#include <qwaylandclientextension.h>
#include "../../toplevel_management/handle.hpp"
#include "../../toplevel/wlr_toplevel.hpp"
#include "wayland-hyprland-toplevel-mapping-v1-client-protocol.h"
namespace qs::hyprland::ipc {
@@ -16,7 +16,7 @@ namespace qs::hyprland::ipc {
class HyprlandToplevelMappingHandle: QtWayland::hyprland_toplevel_window_mapping_handle_v1 {
public:
explicit HyprlandToplevelMappingHandle(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
qs::wayland::toplevel::wlr::ToplevelHandle* handle,
::hyprland_toplevel_window_mapping_handle_v1* mapping
)
: QtWayland::hyprland_toplevel_window_mapping_handle_v1(mapping)
@@ -34,7 +34,7 @@ protected:
void hyprland_toplevel_window_mapping_handle_v1_failed() override;
private:
qs::wayland::toplevel_management::impl::ToplevelHandle* handle;
qs::wayland::toplevel::wlr::ToplevelHandle* handle;
};
class HyprlandToplevelMappingManager
@@ -48,22 +48,18 @@ public:
static HyprlandToplevelMappingManager* instance();
[[nodiscard]] quint64
getToplevelAddress(qs::wayland::toplevel_management::impl::ToplevelHandle* handle) const;
getToplevelAddress(qs::wayland::toplevel::wlr::ToplevelHandle* handle) const;
signals:
void toplevelAddressed(
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
quint64 address
);
void toplevelAddressed(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
private slots:
void onToplevelReady(qs::wayland::toplevel_management::impl::ToplevelHandle* handle);
void onToplevelReady(qs::wayland::toplevel::wlr::ToplevelHandle* handle);
void onToplevelDestroyed(QObject* object);
private:
void
assignAddress(qs::wayland::toplevel_management::impl::ToplevelHandle* handle, quint64 address);
QHash<wayland::toplevel_management::impl::ToplevelHandle*, quint64> addresses;
void assignAddress(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
QHash<wayland::toplevel::wlr::ToplevelHandle*, quint64> addresses;
friend class HyprlandToplevelMappingHandle;
};
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff. Show more