Compare commits

...

34 commits

Author SHA1 Message Date
59e9c47b0e
version: 0.3.0 2026-05-03 22:09:49 -07:00
ffe95f06ac
guix: drop
Hard to keep the package up to date without an active maintainer.
2026-05-03 18:50:15 -07:00
8db8ca1fec
core/log: mask virtual/override property warnings 2026-05-02 00:15:59 -07:00
0baa81aa03
all: fix typos 2026-04-27 18:56:41 -07:00
01834e9c8f
hyprland/ipc: add lua ipc support 2026-04-27 18:56:39 -07:00
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
11a71d233a
dbus/dbusmenu: connect onItemPropertiesUpdated
Evidently this was broken at some point or never connected.
2026-04-24 21:01:49 -07:00
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
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
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
9a54119893
core/command: warn on mismatch when other displays have instances 2026-04-19 02:01:41 -07:00
18075d1218
core/command: ignore QT_QPA_PLATFORM in display check 2026-04-19 02:01:19 -07:00
fb08eced44
wayland/screencopy: intersect dmabuf and render formats + cleanup
Fixes 99% of MGPU cases.
2026-04-13 00:41:08 -07:00
d4c92973b5
i3/ipc: ensure monitor/workspace pointers are nulled on destroy 2026-04-09 00:34:57 -07:00
7f7ab6bc8a
launch: use dup2 to reset daemon stdio over close+open 2026-04-09 00:10:49 -07:00
7208f68bb7
core: add QS_DROP_EXPENSIVE_FONTS env var 2026-04-08 01:35:15 -07:00
f0d0216b3d
core: add DropExpensiveFonts pragma disabling woff and woff2 fonts 2026-04-08 00:48:58 -07:00
7c5a6c4bd4
core/log: crash if Quickshell's log filter is installed twice
Crashes from recursion inside filterCategories through the old filter
have been observed. Presumably this means the log filter is getting
installed twice somehow. This should catch it.
2026-04-06 00:45:26 -07:00
5bf6a412b0
core: correctly construct runtime path when XDG_RUNTIME_DIR missing
Fixes a typo of % as $, which broke string substitution.
2026-04-06 00:43:02 -07:00
13fe9b0d98
services/pipewire: avoid blanket disconnect for default nodes
The same nodes can be both default and default configured nodes. When
the default and default configured node are not changed in unison, a
blanket disconnect will also disconnect the other's destroy handler,
causing a crash if the other is accessed after the node is destroyed.
2026-04-06 00:42:29 -07:00
ad5fd9116e
wm: add nullptr guard to WindowManager::screenProjection 2026-04-04 13:51:32 -07:00
49d4f46cf1
io/fileview: handle deserialization to list<T> properties 2026-04-04 13:05:33 -07:00
9b98d10178
io/fileview: try to convert values to json before handling sequences
The previous code was interpreting a string as a list of characters
and therefore a sequence.
2026-04-04 12:28:40 -07:00
854088c48c
io/fileview: convert containers to QVariantList/Map before serialize
QJsonValue::fromVariant doesn't do this automatically for some reason.
2026-04-04 02:06:22 -07:00
bbedward
b4e71cb2c0
core/window: add parentWindow property to FloatingWindow 2026-04-03 21:36:18 -07:00
ceac3c6cfa
io/fileview: use QVariant when QJSValue cast fails in adapter prop read
A QVariant(QVariantMap) does not convert implicitly to a
QVaraint(QJSValue), causing extra signals to be emitted if the old
value was not updated by js (replaced by a QJSValue) before
deserializing again.
2026-04-03 21:36:02 -07:00
aaff22f4b0
io/fileview: write values into correct JsonObjects in deserialize
Property writes were being done on the JsonAdapter and not the child
JsonObject, resulting in the data of children being set on the
adapter's props, and occasional crashes.
2026-04-03 21:35:11 -07:00
HigherOrderLogic
50cdf98868
core/colorquant: add imageRect option for cropping image 2026-04-03 00:30:27 -07:00
4b751ccb0d
wayland/screencopy: use linear texture filtering over nearest
Fixes pixelated views at scaled resolutions.
2026-04-03 00:03:27 -07:00
Carson Powers
20c691cdf1
networking: add PSK, settings and connection status support 2026-04-02 20:26:08 -07:00
92b336c80c
tooling: ensure intercepts do not overwrite symlinks to cfg files
Intercept-file writes could end up opening an existing vfs symlink
back to the user's actual config instead of creating a new file in the
vfs.
2026-04-02 03:25:42 -07:00
2 * r + 2 * t
d612227740
core/qmlglobal: add shellId, instanceId, appId and launchTime props 2026-03-31 02:49:24 -07:00
116 changed files with 4434 additions and 1627 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
project(quickshell VERSION "0.3.0" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)

View file

@ -20,7 +20,7 @@ set shell id.
- Added the ability to handle move and resize events to FloatingWindow.
- Pipewire service now reconnects if pipewire dies or a protocol error occurs.
- Added pipewire audio peak detection.
- Added initial support for network management.
- Added network management support.
- Added support for grabbing focus from popup windows.
- Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing.
@ -29,10 +29,12 @@ set shell id.
- Added generic WindowManager interface implementing ext-workspace.
- Added ext-background-effect window blur support.
- 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.
@ -41,6 +43,9 @@ set shell id.
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
- 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
@ -64,6 +69,16 @@ set shell id.
- Fixed partial socket reads in greetd and hyprland on slow machines.
- Worked around Qt bug causing crashes when plugging and unplugging monitors.
- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it.
- Fixed ScreencopyView pixelation when scaled.
- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject.
- Fixed JsonAdapter sending unnecessary property changes for primitive values.
- Fixed JsonAdapter serialization for lists.
- 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

View file

@ -56,7 +56,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.2.1";
version = "0.3.0";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers

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

View file

@ -13,6 +13,7 @@
#include <qnumeric.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qrect.h>
#include <qrgb.h>
#include <qthreadpool.h>
#include <qtmetamacros.h>
@ -24,9 +25,15 @@ namespace {
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
}
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
ColorQuantizerOperation::ColorQuantizerOperation(
QUrl* source,
qreal depth,
QRect imageRect,
qreal rescaleSize
)
: source(source)
, maxDepth(depth)
, imageRect(imageRect)
, rescaleSize(rescaleSize) {
this->setAutoDelete(false);
}
@ -37,6 +44,11 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
this->colors.clear();
auto image = QImage(this->source->toLocalFile());
if (this->imageRect.isValid()) {
image = image.copy(this->imageRect);
}
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
&& this->rescaleSize > 0)
{
@ -198,16 +210,27 @@ void ColorQuantizer::setDepth(qreal depth) {
this->mDepth = depth;
emit this->depthChanged();
if (this->componentCompleted) this->quantizeAsync();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
}
}
void ColorQuantizer::setImageRect(QRect imageRect) {
if (this->mImageRect != imageRect) {
this->mImageRect = imageRect;
emit this->imageRectChanged();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
}
}
void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); }
void ColorQuantizer::setRescaleSize(int rescaleSize) {
if (this->mRescaleSize != rescaleSize) {
this->mRescaleSize = rescaleSize;
emit this->rescaleSizeChanged();
if (this->componentCompleted) this->quantizeAsync();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
}
}
@ -221,8 +244,13 @@ void ColorQuantizer::quantizeAsync() {
if (this->liveOperation) this->cancelAsync();
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
this->liveOperation =
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
this->liveOperation = new ColorQuantizerOperation(
&this->mSource,
this->mDepth,
this->mImageRect,
this->mRescaleSize
);
QObject::connect(
this->liveOperation,

View file

@ -5,6 +5,7 @@
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qrect.h>
#include <qrunnable.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@ -16,7 +17,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
void run() override;
void tryCancel();
@ -44,6 +45,7 @@ private:
QList<QColor> colors;
QUrl* source;
qreal maxDepth;
QRect imageRect;
qreal rescaleSize;
};
@ -78,9 +80,16 @@ class ColorQuantizer
/// binary split of the color space
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
// clang-format off
/// Rectangle that the source image is cropped to.
///
/// Can be set to `undefined` to reset.
Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged);
// clang-format on
/// 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:
@ -97,6 +106,10 @@ public:
[[nodiscard]] qreal depth() const { return this->mDepth; }
void setDepth(qreal depth);
[[nodiscard]] QRect imageRect() const { return this->mImageRect; }
void setImageRect(QRect imageRect);
void resetImageRect();
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
void setRescaleSize(int rescaleSize);
@ -104,6 +117,7 @@ signals:
void colorsChanged();
void sourceChanged();
void depthChanged();
void imageRectChanged();
void rescaleSizeChanged();
public slots:
@ -117,6 +131,7 @@ private:
ColorQuantizerOperation* liveOperation = nullptr;
QUrl mSource;
qreal mDepth = 0;
QRect mImageRect;
qreal mRescaleSize = 0;
Q_OBJECT_BINDABLE_PROPERTY(

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*/,

View file

@ -257,11 +257,23 @@ 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) {
} 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) {
filter.applyRule(categoryName, rule);
}
@ -310,10 +322,15 @@ void LogManager::init(
instance->rules->append(parser.rules());
}
qInstallMessageHandler(&LogManager::messageHandler);
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
if (instance->lastCategoryFilter == &LogManager::filterCategory) {
qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug.";
instance->lastCategoryFilter = nullptr;
}
qInstallMessageHandler(&LogManager::messageHandler);
qCDebug(logLogging) << "Creating offthread logger...";
auto* thread = new QThread();
instance->threadProxy.moveToThread(thread);

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

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>
@ -64,7 +64,7 @@ QDir* QsPaths::baseRunDir() {
if (this->baseRunState == DirState::Unknown) {
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
if (runtimeDir.isEmpty()) {
runtimeDir = QString("/run/user/$1").arg(getuid());
runtimeDir = QString("/run/user/%1").arg(getuid());
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
}
@ -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};
}

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

View file

@ -18,14 +18,17 @@
#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>
#include "../io/processcore.hpp"
#include "generation.hpp"
#include "iconimageprovider.hpp"
#include "instanceinfo.hpp"
#include "paths.hpp"
#include "qmlscreen.hpp"
#include "rootwrapper.hpp"
@ -153,6 +156,22 @@ qint32 QuickshellGlobal::processId() const { // NOLINT
return getpid();
}
QString QuickshellGlobal::instanceId() const { // NOLINT
return InstanceInfo::CURRENT.instanceId;
}
QString QuickshellGlobal::shellId() const { // NOLINT
return InstanceInfo::CURRENT.shellId;
}
QString QuickshellGlobal::appId() const { // NOLINT
return InstanceInfo::CURRENT.appId;
}
QDateTime QuickshellGlobal::launchTime() const { // NOLINT
return InstanceInfo::CURRENT.launchTime;
}
qsizetype QuickshellGlobal::screensCount(QQmlListProperty<QuickshellScreenInfo>* /*unused*/) {
return QuickshellTracked::instance()->screens.size();
}
@ -326,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);

View file

@ -17,6 +17,7 @@
#include "../io/processcore.hpp"
#include "doc.hpp"
#include "instanceinfo.hpp"
#include "qmlscreen.hpp"
///! Accessor for some options under the Quickshell type.
@ -83,6 +84,21 @@ class QuickshellGlobal: public QObject {
// clang-format off
/// Quickshell's process id.
Q_PROPERTY(qint32 processId READ processId CONSTANT);
/// A unique identifier for this Quickshell instance
Q_PROPERTY(QString instanceId READ instanceId CONSTANT)
/// The shell ID, used to differentiate between different shell configurations.
///
/// Defaults to a stable value derived from the config path.
/// Can be overridden with `//@ pragma ShellId <id>` in the root qml file.
Q_PROPERTY(QString shellId READ shellId CONSTANT)
/// The desktop application ID.
///
/// Defaults to `org.quickshell`.
/// Can be overridden with `//@ pragma AppId <id>` in the root qml file
/// or the `QS_APP_ID` environment variable.
Q_PROPERTY(QString appId READ appId CONSTANT)
/// The time at which this Quickshell instance was launched.
Q_PROPERTY(QDateTime launchTime READ launchTime CONSTANT)
/// All currently connected screens.
///
/// This property updates as connected screens change.
@ -149,6 +165,10 @@ class QuickshellGlobal: public QObject {
public:
[[nodiscard]] qint32 processId() const;
[[nodiscard]] QString instanceId() const;
[[nodiscard]] QString shellId() const;
[[nodiscard]] QString appId() const;
[[nodiscard]] QDateTime launchTime() const;
QQmlListProperty<QuickshellScreenInfo> screens();
@ -224,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
@ -234,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; }

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

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 {

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;

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.

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;

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());
}

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

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)

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

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();
};

View file

@ -90,7 +90,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"

View file

@ -177,6 +177,8 @@ void QmlToolingSupport::updateToolingFs(
auto fileInfo = QFileInfo(path);
if (!fileInfo.isFile()) continue;
if (scanner.fileIntercepts.contains(path)) continue;
auto spath = linkDir.filePath(name);
auto sFileInfo = QFileInfo(spath);
@ -205,8 +207,10 @@ void QmlToolingSupport::updateToolingFs(
}
auto spath = linkDir.filePath(name);
QFile::remove(spath);
auto file = QFile(spath);
if (!file.open(QFile::ReadWrite | QFile::Text)) {
if (!file.open(QFile::ReadWrite | QFile::Text | QFile::NewOnly)) {
qCCritical(logTooling) << "Failed to open injected file" << spath;
continue;
}

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

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.

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();
}

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

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,

View file

@ -1,11 +1,13 @@
#include "jsonadapter.hpp"
#include <qassociativeiterable.h>
#include <qcontainerfwd.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qjsonvalue.h>
#include <qjsvalue.h>
#include <qmetacontainer.h>
#include <qmetaobject.h>
#include <qnamespace.h>
#include <qobject.h>
@ -14,6 +16,7 @@
#include <qqmlengine.h>
#include <qqmlinfo.h>
#include <qqmllist.h>
#include <qsequentialiterable.h>
#include <qstringview.h>
#include <qvariant.h>
@ -131,13 +134,22 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
}
json.insert(prop.name(), array);
} else if (val.canConvert<QJSValue>()) {
auto variant = val.value<QJSValue>().toVariant();
auto jv = QJsonValue::fromVariant(variant);
json.insert(prop.name(), jv);
} else {
auto jv = QJsonValue::fromVariant(val);
json.insert(prop.name(), jv);
if (val.canConvert<QJSValue>()) val = val.value<QJSValue>().toVariant();
auto jsonVal = QJsonValue::fromVariant(val);
if (jsonVal.isNull() && !val.isNull() && val.isValid()) {
if (val.canConvert<QAssociativeIterable>()) {
val.convert(QMetaType::fromType<QVariantMap>());
} else if (val.canConvert<QSequentialIterable>()) {
val.convert(QMetaType::fromType<QVariantList>());
}
jsonVal = QJsonValue::fromVariant(val);
}
json.insert(prop.name(), jsonVal);
}
}
}
@ -154,14 +166,16 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
auto jval = json.value(prop.name());
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
auto variant = jval.toVariant();
auto oldValue = prop.read(this).value<QJSValue>();
auto newVariant = jval.toVariant();
auto oldValue = prop.read(obj);
auto oldVariant =
oldValue.canConvert<QJSValue>() ? oldValue.value<QJSValue>().toVariant() : oldValue;
// Calling prop.write with a new QJSValue will cause a property update
// even if content is identical.
if (jval.toVariant() != oldValue.toVariant()) {
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(jval.toVariant());
prop.write(this, QVariant::fromValue(jsValue));
if (newVariant != oldVariant) {
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(newVariant);
prop.write(obj, QVariant::fromValue(jsValue));
}
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
@ -196,7 +210,7 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
QMetaType::fromType<QQmlListProperty<JsonObject>>()
))
{
auto pval = prop.read(this);
auto pval = prop.read(obj);
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
auto lp = pval.value<QQmlListProperty<JsonObject>>();
@ -247,12 +261,35 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
}
} else {
auto variant = jval.toVariant();
auto convVariant = variant;
if (variant.convert(prop.metaType())) {
prop.write(obj, variant);
if (convVariant.convert(prop.metaType())) {
prop.write(obj, convVariant);
} else {
auto pval = prop.read(obj);
if (variant.canConvert<QSequentialIterable>() && pval.canView<QSequentialIterable>()) {
auto targetv = QVariant(pval.metaType());
auto target = targetv.view<QSequentialIterable>().metaContainer();
auto valueType = target.valueMetaType();
auto i = 0;
for (QVariant item: variant.value<QSequentialIterable>()) {
if (item.convert(valueType)) {
target.addValueAtEnd(targetv.data(), item.constData());
} else {
qmlWarning(this) << "Failed to deserialize list member " << i << " of property "
<< prop.name() << ": expected " << valueType.name() << " but got "
<< item.typeName();
}
++i;
}
prop.write(obj, targetv);
} else {
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
<< prop.metaType().name() << " but got " << jval.toVariant().typeName();
<< prop.metaType().name() << " but got "
<< jval.toVariant().typeName();
}
}
}
}

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

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

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 {
@ -179,13 +178,17 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
}
} 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,6 +196,18 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
qCInfo(logBare) << "Some instances on other displays match:";
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 {
@ -230,7 +245,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;
@ -251,6 +266,16 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
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;
}
@ -314,7 +339,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()
);
@ -547,16 +572,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";
}
}

View file

@ -76,7 +76,9 @@ 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;
QString stateDir;
QString cacheDir;
@ -88,24 +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.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();
@ -116,8 +124,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
} else if (pragma.startsWith("CacheDir ")) {
pragmas.cacheDir = pragma.sliced(9).trimmed();
} else {
qCritical() << "Unrecognized pragma" << pragma;
return -1;
qWarning() << "Unrecognized pragma" << pragma;
}
} else if (line.startsWith("import")) break;
}
@ -173,10 +180,56 @@ 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());
}
pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1;
if (pragmas.dropExpensiveFonts) {
if (auto* runDir = QsPaths::instance()->instanceRunDir()) {
auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE");
if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf";
auto filterPath = runDir->filePath("fonts-override.conf");
auto filterFile = QFile(filterPath);
if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) {
auto filterTemplate = QStringLiteral(R"(<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<include ignore_missing="no">%1</include>
<selectfont>
<rejectfont>
<pattern>
<patelt name="fontwrapper">
<string>woff</string>
</patelt>
</pattern>
<pattern>
<patelt name="fontwrapper">
<string>woff2</string>
</patelt>
</pattern>
</rejectfont>
</selectfont>
</fontconfig>
)");
QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath);
filterFile.close();
qputenv("FONTCONFIG_FILE", filterPath.toUtf8());
} else {
qCritical() << "Could not write fontconfig filter to" << filterPath;
}
} else {
qCritical() << "Could not create fontconfig filter: instance run directory unavailable";
}
}
// The qml engine currently refuses to cache non file (qsintercept) paths.
// if (auto* cacheDir = QsPaths::instance()->cacheDir()) {

View file

@ -84,21 +84,29 @@ void exitDaemon(int code) {
close(DAEMON_PIPE);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stdin";
auto fd = open("/dev/null", O_RDWR);
if (fd == -1) {
qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": "
<< qt_error_string();
return;
}
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stdout";
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": "
<< qt_error_string();
}
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stderr";
if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": "
<< qt_error_string();
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": "
<< qt_error_string();
}
close(fd);
}
int main(int argc, char** argv) {

View file

@ -3,7 +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

View file

@ -8,6 +8,8 @@
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
@ -15,49 +17,9 @@ namespace {
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
} // namespace
QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Connecting: return QStringLiteral("Connecting");
case Connected: return QStringLiteral("Connected");
case Disconnecting: return QStringLiteral("Disconnecting");
case Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
QString DeviceType::toString(DeviceType::Enum type) {
switch (type) {
case None: return QStringLiteral("None");
case Wifi: return QStringLiteral("Wifi");
default: return QStringLiteral("Unknown");
}
}
QString NMDeviceState::toString(NMDeviceState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Unmanaged: return QStringLiteral("Not managed by NetworkManager");
case Unavailable: return QStringLiteral("Unavailable");
case Disconnected: return QStringLiteral("Disconnected");
case Prepare: return QStringLiteral("Preparing to connect");
case Config: return QStringLiteral("Connecting to a network");
case NeedAuth: return QStringLiteral("Waiting for authentication");
case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network");
case IPCheck:
return QStringLiteral("Checking if further action is required for the requested connection");
case Secondaries:
return QStringLiteral("Waiting for a required secondary connection to activate");
case Activated: return QStringLiteral("Connected");
case Deactivating: return QStringLiteral("Disconnecting");
case Failed: return QStringLiteral("Failed to connect");
default: return QStringLiteral("Unknown");
};
}
NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) {
this->bindableConnected().setBinding([this]() {
return this->bState == DeviceConnectionState::Connected;
return this->bState == ConnectionState::Connected;
});
};
@ -66,12 +28,17 @@ void NetworkDevice::setAutoconnect(bool autoconnect) {
emit this->requestSetAutoconnect(autoconnect);
}
void NetworkDevice::setNmManaged(bool managed) {
if (this->bNmManaged == managed) return;
emit this->requestSetNmManaged(managed);
}
void NetworkDevice::disconnect() {
if (this->bState == DeviceConnectionState::Disconnected) {
if (this->bState == ConnectionState::Disconnected) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
return;
}
if (this->bState == DeviceConnectionState::Disconnecting) {
if (this->bState == ConnectionState::Disconnecting) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
return;
}
@ -79,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

View file

@ -6,88 +6,44 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! Connection state of a NetworkDevice.
class DeviceConnectionState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state);
};
///! Type of network device.
class DeviceType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
};
///! NetworkManager-specific device state.
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
class NMDeviceState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IPConfig = 70,
IPCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NMDeviceState::Enum state);
};
///! A network device.
/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points.
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
class NetworkDevice: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("Devices can only be acquired through Network");
// clang-format off
/// The device type.
///
/// 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.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// Connection state of the device.
Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// A more specific device state when the backend is NetworkManager.
Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState);
/// True if the device is allowed to autoconnect.
Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the device is managed by NetworkManager.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged)
/// True if the device is allowed to autoconnect to a network.
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
// clang-format on
@ -97,35 +53,45 @@ public:
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
[[nodiscard]] DeviceType::Enum type() const { return this->mType; };
QBindable<QString> bindableName() { return &this->bName; };
[[nodiscard]] QString name() const { return this->bName; };
QBindable<QString> bindableAddress() { return &this->bAddress; };
QBindable<bool> bindableConnected() { return &this->bConnected; };
QBindable<DeviceConnectionState::Enum> bindableState() { return &this->bState; };
QBindable<NMDeviceState::Enum> bindableNmState() { return &this->bNmState; };
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; };
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; }
QBindable<QString> bindableAddress() { return &this->bAddress; }
QBindable<bool> bindableConnected() { return &this->bConnected; }
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
QBindable<bool> bindableNmManaged() { return &this->bNmManaged; }
[[nodiscard]] bool nmManaged() { return this->bNmManaged; }
void setNmManaged(bool managed);
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; }
[[nodiscard]] bool autoconnect() { return this->bAutoconnect; }
void setAutoconnect(bool autoconnect);
signals:
void requestDisconnect();
void requestSetAutoconnect(bool autoconnect);
QSDOC_HIDE void requestDisconnect();
QSDOC_HIDE void requestSetAutoconnect(bool autoconnect);
QSDOC_HIDE void requestSetNmManaged(bool managed);
void nameChanged();
void addressChanged();
void connectedChanged();
void stateChanged();
void nmStateChanged();
void nmManagedChanged();
void autoconnectChanged();
protected:
ObjectModel<Network> mNetworks {this};
private:
DeviceType::Enum mType;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, ConnectionState::Enum, bState, &NetworkDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
// clang-format on
};

87
src/network/enums.cpp Normal file
View file

@ -0,0 +1,87 @@
#include "enums.hpp"
#include <qstring.h>
namespace qs::network {
QString NetworkConnectivity::toString(NetworkConnectivity::Enum conn) {
switch (conn) {
case Unknown: return QStringLiteral("Unknown");
case None: return QStringLiteral("Not connected to a network");
case Portal: return QStringLiteral("Connection intercepted by a captive portal");
case Limited: return QStringLiteral("Partial internet connectivity");
case Full: return QStringLiteral("Full internet connectivity");
default: return QStringLiteral("Unknown");
}
}
QString NetworkBackendType::toString(NetworkBackendType::Enum type) {
switch (type) {
case NetworkBackendType::None: return "None";
case NetworkBackendType::NetworkManager: return "NetworkManager";
default: return "Unknown";
}
}
QString ConnectionState::toString(ConnectionState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Connecting: return QStringLiteral("Connecting");
case Connected: return QStringLiteral("Connected");
case Disconnecting: return QStringLiteral("Disconnecting");
case Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
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 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");
default: return QStringLiteral("Unknown");
}
}
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");
}
}
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
switch (type) {
case Unknown: return QStringLiteral("Unknown");
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
case Sae: return QStringLiteral("WPA3");
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
case Wpa2Psk: return QStringLiteral("WPA2");
case WpaEap: return QStringLiteral("WPA Enterprise");
case WpaPsk: return QStringLiteral("WPA");
case StaticWep: return QStringLiteral("WEP");
case DynamicWep: return QStringLiteral("Dynamic WEP");
case Leap: return QStringLiteral("LEAP");
case Owe: return QStringLiteral("OWE");
case Open: return QStringLiteral("Open");
default: return QStringLiteral("Unknown");
}
}
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
switch (mode) {
case Unknown: return QStringLiteral("Unknown");
case AdHoc: return QStringLiteral("Ad-Hoc");
case Station: return QStringLiteral("Station");
case AccessPoint: return QStringLiteral("Access Point");
case Mesh: return QStringLiteral("Mesh");
default: return QStringLiteral("Unknown");
};
}
} // namespace qs::network

155
src/network/enums.hpp Normal file
View file

@ -0,0 +1,155 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtypes.h>
namespace qs::network {
///! The degree to which the host can reach the internet.
class NetworkConnectivity: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// Network connectivity is unknown. This means the connectivity checks are disabled or have not run yet.
Unknown = 0,
/// The host is not connected to any network.
None = 1,
/// The internet connection is hijacked by a captive portal gateway.
/// This indicates the shell should open a sandboxed web browser window for the purpose of authenticating to a gateway.
Portal = 2,
/// The host is connected to a network but does not appear to be able to reach the full internet.
Limited = 3,
/// The host is connected to a network and appears to be able to reach the full internet.
Full = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkConnectivity::Enum conn);
};
///! The backend supplying the Network service.
class NetworkBackendType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
NetworkManager = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkBackendType::Enum type);
};
///! The connection state of a device or network.
class ConnectionState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(ConnectionState::Enum state);
};
///! The reason a connection failed.
class ConnectionFailReason: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// The connection failed for an unknown reason.
Unknown = 0,
/// Secrets were required, but not provided.
NoSecrets = 1,
/// The Wi-Fi supplicant disconnected.
WifiClientDisconnected = 2,
/// The Wi-Fi supplicant failed.
WifiClientFailed = 3,
/// The Wi-Fi connection took too long to authenticate.
WifiAuthTimeout = 4,
/// The Wi-Fi network could not be found.
WifiNetworkLost = 5,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(ConnectionFailReason::Enum reason);
};
///! Type of a @@NetworkDevice.
class DeviceType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
};
///! The security type of a @@WifiNetwork.
class WifiSecurityType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Wpa3SuiteB192 = 0,
Sae = 1,
Wpa2Eap = 2,
Wpa2Psk = 3,
WpaEap = 4,
WpaPsk = 5,
StaticWep = 6,
DynamicWep = 7,
Leap = 8,
Owe = 9,
Open = 10,
Unknown = 11,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
};
///! The 802.11 mode of a @@WifiDevice.
class WifiDeviceMode: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// The device is part of an Ad-Hoc network without a central access point.
AdHoc = 0,
/// The device is a station that can connect to networks.
Station = 1,
/// The device is a local hotspot/access point.
AccessPoint = 2,
/// The device is an 802.11s mesh point.
Mesh = 3,
/// The device mode is unknown.
Unknown = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
};
} // namespace qs::network

View file

@ -1,9 +1,13 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"qml.hpp",
"network.hpp",
"device.hpp",
"wifi.hpp",
"wired.hpp",
"enums.hpp",
"nm/settings.hpp",
]
-----
This module exposes Network management APIs provided by a supported network backend.

View file

@ -1,15 +1,15 @@
#include "network.hpp"
#include <utility>
#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 "nm/backend.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
namespace qs::network {
@ -17,49 +17,54 @@ namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
QString NetworkState::toString(NetworkState::Enum state) {
switch (state) {
case NetworkState::Connecting: return QStringLiteral("Connecting");
case NetworkState::Connected: return QStringLiteral("Connected");
case NetworkState::Disconnecting: return QStringLiteral("Disconnecting");
case NetworkState::Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
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 == NetworkState::Connecting || state == NetworkState::Disconnecting;
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
});
};
void Network::connect() {
if (this->bConnected) {
qCCritical(logNetwork) << this << "is already connected.";
return;
}
this->requestConnect();
}
void Network::connectWithSettings(NMSettings* settings) {
if (this->bConnected) {
qCCritical(logNetwork) << this << "is already connected.";
return;
}
if (this->bNmSettings.value().indexOf(settings) == -1) return;
this->requestConnectWithSettings(settings);
}
void Network::disconnect() {
if (!this->bConnected) {
qCCritical(logNetwork) << this << "is not currently connected";
return;
}
this->requestDisconnect();
}
void Network::forget() { this->requestForget(); }
void Network::settingsAdded(NMSettings* settings) {
auto list = this->bNmSettings.value();
if (list.contains(settings)) return;
list.append(settings);
this->bNmSettings = list;
}
void Network::settingsRemoved(NMSettings* settings) {
auto list = this->bNmSettings.value();
list.removeOne(settings);
this->bNmSettings = list;
}
} // namespace qs::network

View file

@ -6,137 +6,102 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/model.hpp"
#include "device.hpp"
#include "../core/doc.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
namespace qs::network {
class NetworkDevice;
}
namespace qs::network {
///! The connection state of a Network.
class NetworkState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkState::Enum state);
};
///! The backend supplying the Network service.
class NetworkBackendType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
NetworkManager = 1,
};
Q_ENUM(Enum);
};
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
///! 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 Networking: public QObject {
Q_OBJECT;
QML_SINGLETON;
QML_ELEMENT;
// clang-format off
/// A list of all network 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);
// clang-format on
public:
explicit Networking(QObject* parent = nullptr);
[[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; };
signals:
void requestSetWifiEnabled(bool enabled);
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
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);
// clang-format on
};
///! A network.
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
class Network: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
QML_UNCREATABLE("Network can only be aqcuired through networking devices");
// clang-format off
/// The name of the network.
Q_PROPERTY(QString name READ name CONSTANT);
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 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.
Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown);
/// The connectivity state of the network.
Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
// 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)
/// > signal will be emitted with `NoSecrets`.
/// > @@WifiNetwork.connectWithPsk() can be used to provide secrets.
Q_INVOKABLE void connect();
/// Attempt to connect to the network with a specific @@nmSettings entry.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_INVOKABLE void connectWithSettings(NMSettings* settings);
/// Disconnect from the network.
Q_INVOKABLE void disconnect();
/// Forget all connection settings for this network.
Q_INVOKABLE void forget();
[[nodiscard]] QString name() const { return this->mName; };
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
// clang-format off
[[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; }
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
QBindable<bool> bindableKnown() { return &this->bKnown; }
[[nodiscard]] ConnectionState::Enum state() const { return this->bState; }
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
// clang-format on
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();
void stateChangingChanged();
void nmSettingsChanged();
QSDOC_HIDE void requestConnect();
QSDOC_HIDE void requestConnectWithSettings(NMSettings* settings);
QSDOC_HIDE void requestDisconnect();
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, NetworkState::Enum, bState, &Network::stateChanged);
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

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
@ -63,10 +73,14 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
qt_add_library(quickshell-network-nm STATIC
backend.cpp
device.cpp
connection.cpp
active_connection.cpp
settings.cpp
accesspoint.cpp
wireless.cpp
wired.cpp
utils.cpp
dbus_types.cpp
network.cpp
enums.hpp
${NM_DBUS_INTERFACES}
)
@ -77,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)

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"
@ -48,14 +48,14 @@ public:
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QByteArray ssid() const { return this->bSsid; };
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; };
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; };
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; };
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; };
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
[[nodiscard]] QByteArray ssid() const { return this->bSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; }
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; }
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; }
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; }
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
signals:
void loaded();

View file

@ -0,0 +1,68 @@
#include "active_connection.hpp"
#include <qcontainerfwd.h>
#include <qdbusconnection.h>
#include <qdbuspendingcall.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "dbus_nm_active_connection.h"
#include "enums.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
this->proxy = new DBusNMActiveConnectionProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->proxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
return;
}
// clang-format off
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
// clang-format on
this->activeConnectionProperties.setInterface(this->proxy);
this->activeConnectionProperties.updateAllViaGetAll();
}
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
if (this->bStateReason == enumReason) return;
this->bStateReason = enumReason;
}
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
QString NMActiveConnection::address() const {
return this->proxy ? this->proxy->service() : QString();
}
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
} // namespace qs::network
namespace qs::dbus {
DBusResult<qs::network::NMConnectionState::Enum>
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
}
} // namespace qs::dbus

View file

@ -1,18 +1,16 @@
#pragma once
#include <qbytearray.h>
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qqmlintegration.h>
#include <qstringlist.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "dbus_nm_active_connection.h"
#include "dbus_nm_connection_settings.h"
#include "dbus_types.hpp"
#include "enums.hpp"
namespace qs::dbus {
@ -28,40 +26,6 @@ struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
class NMConnectionSettings: public QObject {
Q_OBJECT;
public:
explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr);
void forget();
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; };
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
signals:
void loaded();
void settingsChanged(ConnectionSettingsMap settings);
void securityChanged(WifiSecurityType::Enum security);
void ssidChanged(QString ssid);
private:
bool mLoaded = false;
void updateSettings();
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties);
// clang-format on
DBusNMConnectionSettingsProxy* proxy = nullptr;
};
// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object.
class NMActiveConnection: public QObject {
Q_OBJECT;
@ -72,31 +36,27 @@ public:
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; };
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; };
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; }
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->bStateReason; }
signals:
void loaded();
void connectionChanged(QDBusObjectPath path);
void stateChanged(NMConnectionState::Enum state);
void stateReasonChanged(NMConnectionStateReason::Enum reason);
void uuidChanged(const QString& uuid);
private slots:
void onStateChanged(quint32 state, quint32 reason);
private:
NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionStateReason::Enum, bStateReason, &NMActiveConnection::stateReasonChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection");
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pUuid, bUuid, activeConnectionProperties, "Uuid");
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State");
// clang-format on
DBusNMActiveConnectionProxy* proxy = nullptr;

View file

@ -1,5 +1,6 @@
#include "backend.hpp"
#include <qbytearray.h>
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
#include <qdbusmetatype.h>
@ -14,14 +15,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.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 {
@ -31,7 +31,8 @@ QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWa
}
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
qDBusRegisterMetaType<ConnectionSettingsMap>();
qCDebug(logNetworkManager) << "Connecting to NetworkManager";
qDBusRegisterMetaType<NMSettingsMap>();
auto bus = QDBusConnection::systemBus();
if (!bus.isConnected()) {
@ -69,6 +70,23 @@ void NetworkManager::init() {
this->registerDevices();
}
void NetworkManager::checkConnectivity() {
auto pending = this->proxy->CheckConnectivity();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<quint32> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager) << "Failed to check connectivity: " << reply.error().message();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NetworkManager::registerDevices() {
auto pending = this->proxy->GetAllDevices();
auto* call = new QDBusPendingCallWatcher(pending, this);
@ -113,27 +131,27 @@ 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;
}
if (dev) {
qCDebug(logNetworkManager) << "Device added:" << path;
if (!dev->isValid()) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
delete dev;
} else {
this->mDevices[path] = dev;
// Only register a frontend device while it's managed by NM.
auto onManagedChanged = [this, dev, type](bool managed) {
managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev);
};
// clang-format off
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged);
// clang-format on
if (dev->managed()) 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;
}
temp->deleteLater();
}
@ -142,66 +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 DeviceConnectionState::Unknown;
case 30: return DeviceConnectionState::Disconnected;
case 40 ... 90: return DeviceConnectionState::Connecting;
case 100: return DeviceConnectionState::Connected;
case 110 ... 120: return DeviceConnectionState::Disconnecting;
}
};
// clang-format off
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); });
frontendDev->bindableState().setBinding(translateState);
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
// 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());
}
@ -215,7 +173,8 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
auto* dev = iter.value();
this->mDevices.erase(iter);
if (dev) {
this->removeFrontendDevice(dev);
qCDebug(logNetworkManager) << "Device removed:" << path.path();
emit this->deviceRemoved(dev->frontend());
delete dev;
}
}
@ -240,7 +199,7 @@ void NetworkManager::activateConnection(
}
void NetworkManager::addAndActivateConnection(
const ConnectionSettingsMap& settings,
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
) {
@ -259,6 +218,12 @@ void NetworkManager::addAndActivateConnection(
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NetworkManager::setConnectivityCheckEnabled(bool enabled) {
if (enabled == this->bConnectivityCheckEnabled) return;
this->bConnectivityCheckEnabled = enabled;
this->pConnectivityCheckEnabled.write();
}
void NetworkManager::setWifiEnabled(bool enabled) {
if (enabled == this->bWifiEnabled) return;
this->bWifiEnabled = enabled;
@ -268,3 +233,12 @@ void NetworkManager::setWifiEnabled(bool enabled) {
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
} // namespace qs::network
namespace qs::dbus {
DBusResult<qs::network::NMConnectivityState::Enum>
DBusDataTransform<qs::network::NMConnectivityState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMConnectivityState::Enum>(wire));
}
} // namespace qs::dbus

View file

@ -8,9 +8,22 @@
#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"
#include "enums.hpp"
namespace qs::dbus {
template <>
struct DBusDataTransform<qs::network::NMConnectivityState::Enum> {
using Wire = quint32;
using Data = qs::network::NMConnectivityState::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
@ -21,24 +34,34 @@ public:
explicit NetworkManager(QObject* parent = nullptr);
[[nodiscard]] bool isAvailable() const override;
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; };
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; }
[[nodiscard]] bool connectivityCheckAvailable() const {
return this->bConnectivityCheckAvailable;
};
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
[[nodiscard]] NMConnectivityState::Enum connectivity() const { return this->bConnectivity; }
signals:
void deviceAdded(NetworkDevice* device);
void deviceRemoved(NetworkDevice* device);
void wifiEnabledChanged(bool enabled);
void wifiHardwareEnabledChanged(bool enabled);
void connectivityStateChanged(NMConnectivityState::Enum state);
void connectivityCheckAvailableChanged(bool available);
void connectivityCheckEnabledChanged(bool enabled);
public slots:
void setWifiEnabled(bool enabled);
void setConnectivityCheckEnabled(bool enabled);
void checkConnectivity();
private slots:
void onDevicePathAdded(const QDBusObjectPath& path);
void onDevicePathRemoved(const QDBusObjectPath& path);
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const ConnectionSettingsMap& settings,
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
);
@ -47,19 +70,22 @@ 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);
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, NMConnectivityState::Enum, bConnectivity, &NetworkManager::connectivityStateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckAvailable, &NetworkManager::connectivityCheckAvailableChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckEnabled, &NetworkManager::connectivityCheckEnabledChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivity, bConnectivity, dbusProperties, "Connectivity");
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckAvailable, bConnectivityCheckAvailable, dbusProperties, "ConnectivityCheckAvailable");
QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled");
// clang-format on
DBusNetworkManagerProxy* proxy = nullptr;
};

View file

@ -1,151 +0,0 @@
#include "connection.hpp"
#include <qdbusconnection.h>
#include <qdbusmetatype.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "dbus_nm_active_connection.h"
#include "dbus_nm_connection_settings.h"
#include "dbus_types.hpp"
#include "enums.hpp"
#include "utils.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent): QObject(parent) {
qDBusRegisterMetaType<ConnectionSettingsMap>();
this->proxy = new DBusNMConnectionSettingsProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->proxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
return;
}
QObject::connect(
this->proxy,
&DBusNMConnectionSettingsProxy::Updated,
this,
&NMConnectionSettings::updateSettings
);
this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); });
this->connectionSettingsProperties.setInterface(this->proxy);
this->connectionSettingsProperties.updateAllViaGetAll();
this->updateSettings();
}
void NMConnectionSettings::updateSettings() {
auto pending = this->proxy->GetSettings();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<ConnectionSettingsMap> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to get" << this->path() << "settings:" << reply.error().message();
} else {
this->bSettings = reply.value();
}
if (!this->mLoaded) {
emit this->loaded();
this->mLoaded = true;
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NMConnectionSettings::forget() {
auto pending = this->proxy->Delete();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to forget" << this->path() << ":" << reply.error().message();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
bool NMConnectionSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
QString NMConnectionSettings::address() const {
return this->proxy ? this->proxy->service() : QString();
}
QString NMConnectionSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
this->proxy = new DBusNMActiveConnectionProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->proxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
return;
}
// clang-format off
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
// clang-format on
this->activeConnectionProperties.setInterface(this->proxy);
this->activeConnectionProperties.updateAllViaGetAll();
}
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
if (this->mStateReason == enumReason) return;
this->mStateReason = enumReason;
emit this->stateReasonChanged(enumReason);
}
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
QString NMActiveConnection::address() const {
return this->proxy ? this->proxy->service() : QString();
}
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
} // namespace qs::network
namespace qs::dbus {
DBusResult<qs::network::NMConnectionState::Enum>
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
}
} // namespace qs::dbus

View file

@ -0,0 +1,69 @@
#include "dbus_types.hpp"
#include <qbytearray.h>
#include <qcontainerfwd.h>
#include <qdbusargument.h>
#include <qmap.h>
#include <qmetatype.h>
#include <qstring.h>
namespace qs::network {
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map) {
argument.beginMap();
while (!argument.atEnd()) {
argument.beginMapEntry();
QString groupName;
argument >> groupName;
QVariantMap group;
argument >> group;
map.insert(groupName, group);
argument.endMapEntry();
}
argument.endMap();
return argument;
}
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map) {
argument.beginMap(qMetaTypeId<QString>(), qMetaTypeId<QVariantMap>());
for (auto it = map.constBegin(); it != map.constEnd(); ++it) {
argument.beginMapEntry();
argument << it.key();
argument << it.value();
argument.endMapEntry();
}
argument.endMap();
return argument;
}
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Address& addr) {
argument.beginStructure();
argument >> addr.address >> addr.prefix >> addr.gateway;
argument.endStructure();
return argument;
}
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Address& addr) {
argument.beginStructure();
argument << addr.address << addr.prefix << addr.gateway;
argument.endStructure();
return argument;
}
const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Route& route) {
argument.beginStructure();
argument >> route.destination >> route.prefix >> route.nexthop >> route.metric;
argument.endStructure();
return argument;
}
const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Route& route) {
argument.beginStructure();
argument << route.destination << route.prefix << route.nexthop << route.metric;
argument.endStructure();
return argument;
}
} // namespace qs::network

View file

@ -1,9 +1,40 @@
#pragma once
#include <qdbusextratypes.h>
#include <qbytearray.h>
#include <qdbusargument.h>
#include <qmap.h>
#include <qstring.h>
#include <qtypes.h>
#include <qvariant.h>
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
Q_DECLARE_METATYPE(ConnectionSettingsMap);
namespace qs::network {
using NMSettingsMap = QMap<QString, QVariantMap>;
const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map);
const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map);
struct NMIPv6Address {
QByteArray address;
quint32 prefix = 0;
QByteArray gateway;
};
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Address& addr);
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Address& addr);
struct NMIPv6Route {
QByteArray destination;
quint32 prefix = 0;
QByteArray nexthop;
quint32 metric = 0;
};
const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Route& route);
const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Route& route);
} // namespace qs::network
Q_DECLARE_METATYPE(qs::network::NMSettingsMap);
Q_DECLARE_METATYPE(qs::network::NMIPv6Address);
Q_DECLARE_METATYPE(qs::network::NMIPv6Route);

View file

@ -15,8 +15,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "connection.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 {
using namespace qs::dbus;
@ -39,19 +44,68 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
}
// clang-format off
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
QObject::connect(this, &NMDevice::availableSettingsPathsChanged, this, &NMDevice::onAvailableSettingsPathsChanged);
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
QObject::connect(this->deviceProxy, &DBusNMDeviceProxy::StateChanged, this, &NMDevice::onStateChanged);
// clang-format on
this->deviceProperties.setInterface(this->deviceProxy);
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);
if (enumNewState == NMDeviceState::Failed) this->bLastFailReason = enumReason;
if (this->bStateReason == enumReason) return;
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();
// Remove old active connection
if (this->mActiveConnection) {
qCDebug(logNetworkManager) << "Active connection removed:" << this->mActiveConnection->path();
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
delete this->mActiveConnection;
this->mActiveConnection = nullptr;
@ -64,6 +118,7 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
delete active;
} else {
qCDebug(logNetworkManager) << "Active connection added:" << stringPath;
this->mActiveConnection = active;
QObject::connect(
active,
@ -76,42 +131,44 @@ void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
}
}
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
void NMDevice::onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths) {
QSet<QString> newPathSet;
for (const QDBusObjectPath& path: paths) {
newPathSet.insert(path.path());
}
const auto existingPaths = this->mConnections.keys();
const auto existingPaths = this->mSettings.keys();
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
const auto addedConnections = newPathSet - existingPathSet;
const auto removedConnections = existingPathSet - newPathSet;
const auto addedSettings = newPathSet - existingPathSet;
const auto removedSettings = existingPathSet - newPathSet;
for (const QString& path: addedConnections) {
this->registerConnection(path);
for (const QString& path: addedSettings) {
this->registerSettings(path);
}
for (const QString& path: removedConnections) {
auto* connection = this->mConnections.take(path);
for (const QString& path: removedSettings) {
auto* connection = this->mSettings.take(path);
if (!connection) {
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
} else {
qCDebug(logNetworkManager) << "Connection settings removed:" << path;
delete connection;
}
};
}
void NMDevice::registerConnection(const QString& path) {
auto* connection = new NMConnectionSettings(path, this);
if (!connection->isValid()) {
void NMDevice::registerSettings(const QString& path) {
auto* settings = new NMSettings(path, this);
if (!settings->isValid()) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
delete connection;
delete settings;
} else {
this->mConnections.insert(path, connection);
qCDebug(logNetworkManager) << "Connection settings added:" << path;
this->mSettings.insert(path, settings);
QObject::connect(
connection,
&NMConnectionSettings::loaded,
settings,
&NMSettings::loaded,
this,
[this, connection]() { emit this->connectionLoaded(connection); },
[this, settings]() { emit this->settingsLoaded(settings); },
Qt::SingleShotConnection
);
}
@ -125,6 +182,12 @@ void NMDevice::setAutoconnect(bool autoconnect) {
this->pAutoconnect.write();
}
void NMDevice::setManaged(bool managed) {
if (managed == this->bManaged) return;
this->bManaged = managed;
this->pManaged.write();
}
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
QString NMDevice::address() const {
return this->deviceProxy ? this->deviceProxy->service() : QString();
@ -140,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

View file

@ -8,8 +8,12 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "connection.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 {
@ -20,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 {
@ -36,43 +47,61 @@ public:
[[nodiscard]] virtual bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QString interface() const { return this->bInterface; };
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; };
[[nodiscard]] bool managed() const { return this->bManaged; };
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; };
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; };
[[nodiscard]] QString interface() const { return this->bInterface; }
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; }
[[nodiscard]] bool managed() const { return this->bManaged; }
[[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 ConnectionSettingsMap& settings,
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& apPath
const QDBusObjectPath& specificObjectPath
);
void connectionLoaded(NMConnectionSettings* connection);
void connectionRemoved(NMConnectionSettings* connection);
void availableConnectionPathsChanged(QList<QDBusObjectPath> paths);
void networkAdded(Network* net);
void networkRemoved(Network* net);
void settingsLoaded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
void activeConnectionPathChanged(const QDBusObjectPath& connection);
void activeConnectionLoaded(NMActiveConnection* active);
void interfaceChanged(const QString& interface);
void hwAddressChanged(const QString& hwAddress);
void managedChanged(bool managed);
void stateChanged(NMDeviceState::Enum state);
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 onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
private:
void registerConnection(const QString& path);
void registerSettings(const QString& path);
QHash<QString, NMConnectionSettings*> mConnections;
QHash<QString, NMSettings*> mSettings;
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
@ -80,9 +109,12 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bStateReason, &NMDevice::stateReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bLastFailReason, &NMDevice::lastFailReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
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");
@ -92,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;

View file

@ -7,6 +7,20 @@
namespace qs::network {
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMConnectivityState
class NMConnectivityState: public QObject {
Q_OBJECT;
public:
enum Enum : quint8 {
Unknown = 0,
None = 1,
Portal = 2,
Limited = 3,
Full = 4,
};
};
// Indicates the type of hardware represented by a device object.
class NMDeviceType: public QObject {
Q_OBJECT;
@ -52,6 +66,140 @@ public:
Q_ENUM(Enum);
};
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
class NMDeviceState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IPConfig = 70,
IPCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
};
Q_ENUM(Enum);
};
// Device state change reason codes.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceStateReason.
class NMDeviceStateReason: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Unknown = 1,
NowManaged = 2,
NowUnmanaged = 3,
ConfigFailed = 4,
IpConfigUnavailable = 5,
IpConfigExpired = 6,
NoSecrets = 7,
SupplicantDisconnect = 8,
SupplicantConfigFailed = 9,
SupplicantFailed = 10,
SupplicantTimeout = 11,
PppStartFailed = 12,
PppDisconnect = 13,
PppFailed = 14,
DhcpStartFailed = 15,
DhcpError = 16,
DhcpFailed = 17,
SharedStartFailed = 18,
SharedFailed = 19,
AutoIpStartFailed = 20,
AutoIpError = 21,
AutoIpFailed = 22,
ModemBusy = 23,
ModemNoDialTone = 24,
ModemNoCarrier = 25,
ModemDialTimeout = 26,
ModemDialFailed = 27,
ModemInitFailed = 28,
GsmApnFailed = 29,
GsmRegistrationNotSearching = 30,
GsmRegistrationDenied = 31,
GsmRegistrationTimeout = 32,
GsmRegistrationFailed = 33,
GsmPinCheckFailed = 34,
FirmwareMissing = 35,
Removed = 36,
Sleeping = 37,
ConnectionRemoved = 38,
UserRequested = 39,
Carrier = 40,
ConnectionAssumed = 41,
SupplicantAvailable = 42,
ModemNotFound = 43,
BtFailed = 44,
GsmSimNotInserted = 45,
GsmSimPinRequired = 46,
GsmSimPukRequired = 47,
GsmSimWrong = 48,
InfinibandMode = 49,
DependencyFailed = 50,
Br2684Failed = 51,
ModemManagerUnavailable = 52,
SsidNotFound = 53,
SecondaryConnectionFailed = 54,
DcbFcoeFailed = 55,
TeamdControlFailed = 56,
ModemFailed = 57,
ModemAvailable = 58,
SimPinIncorrect = 59,
NewActivation = 60,
ParentChanged = 61,
ParentManagedChanged = 62,
OvsdbFailed = 63,
IpAddressDuplicate = 64,
IpMethodUnsupported = 65,
SriovConfigurationFailed = 66,
PeerNotFound = 67,
DeviceHandlerFailed = 68,
UnmanagedByDefault = 69,
UnmanagedExternalDown = 70,
UnmanagedLinkNotInit = 71,
UnmanagedQuitting = 72,
UnmanagedManagerDisabled = 73,
UnmanagedUserConf = 74,
UnmanagedUserExplicit = 75,
UnmanagedUserSettings = 76,
UnmanagedUserUdev = 77,
NetworkingOff = 78,
};
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 {
@ -153,4 +301,31 @@ public:
Q_ENUM(Enum);
};
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason.
class NMConnectionStateReason: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
None = 1,
UserDisconnected = 2,
DeviceDisconnected = 3,
ServiceStopped = 4,
IpConfigInvalid = 5,
ConnectTimeout = 6,
ServiceStartTimeout = 7,
ServiceStartFailed = 8,
NoSecrets = 9,
LoginFailed = 10,
ConnectionRemoved = 11,
DependencyFailed = 12,
DeviceRealizeFailed = 13,
DeviceRemoved = 14
};
Q_ENUM(Enum);
};
} // namespace qs::network

308
src/network/nm/network.cpp Normal file
View file

@ -0,0 +1,308 @@
#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())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
}
};
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())) {
emit this->apRemoved(ap);
this->updateReferenceAp();
}
};
// 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
src/network/nm/network.hpp Normal file
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

View file

@ -0,0 +1,4 @@
<node>
<interface name="org.freedesktop.NetworkManager.Device.Wired">
</interface>
</node>

View file

@ -1,5 +1,10 @@
<node>
<interface name="org.freedesktop.NetworkManager.Device">
<method name="Disconnect"/>
<signal name="StateChanged">
<arg name="new_state" type="u"/>
<arg name="old_state" type="u"/>
<arg name="reason" type="u"/>
</signal>
</interface>
</node>

View file

@ -2,8 +2,18 @@
<interface name="org.freedesktop.NetworkManager.Settings.Connection">
<method name="GetSettings">
<arg name="settings" type="a{sa{sv}}" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ConnectionSettingsMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
</method>
<method name="GetSecrets">
<arg name="setting_name" type="s" direction="in"/>
<arg name="secrets" type="a{sa{sv}}" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="qs::network::NMSettingsMap"/>
</method>
<method name="Update">
<arg name="properties" type="a{sa{sv}}" direction="in"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
</method>
<method name="ClearSecrets"/>
<method name="Delete"/>
<signal name="Updated"/>
<signal name="Removed"/>

View file

@ -1,5 +1,8 @@
<node>
<interface name="org.freedesktop.NetworkManager">
<method name="CheckConnectivity">
<arg direction="out" type="u" name="connectivity"/>
</method>
<method name="GetAllDevices">
<arg direction="out" type="ao" name="devices"/>
</method>
@ -11,7 +14,7 @@
</method>
<method name="AddAndActivateConnection">
<arg direction="in" type="a{sa{sv}}" name="connection"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ConnectionSettingsMap"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="qs::network::NMSettingsMap"/>
<arg direction="in" type="o" name="device"/>
<arg direction="in" type="o" name="specific_object"/>
<arg direction="out" type="o" name="path"/>

228
src/network/nm/settings.cpp Normal file
View file

@ -0,0 +1,228 @@
#include "settings.hpp"
#include <qcontainerfwd.h>
#include <qdbusconnection.h>
#include <qdbusmetatype.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qmetatype.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "dbus_nm_connection_settings.h"
#include "dbus_types.hpp"
#include "utils.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network", QtWarningMsg);
QS_LOGGING_CATEGORY(logNMSettings, "quickshell.network.nm_settings", QtWarningMsg);
} // namespace
NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
qDBusRegisterMetaType<QList<QVariantMap>>();
qDBusRegisterMetaType<QList<quint32>>();
qDBusRegisterMetaType<QList<QList<quint32>>>();
qDBusRegisterMetaType<QList<QByteArray>>();
qDBusRegisterMetaType<NMIPv6Address>();
qDBusRegisterMetaType<QList<NMIPv6Address>>();
qDBusRegisterMetaType<NMIPv6Route>();
qDBusRegisterMetaType<QList<NMIPv6Route>>();
qDBusRegisterMetaType<QMap<QString, QString>>();
this->proxy = new DBusNMConnectionSettingsProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->proxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection settings at"
<< path;
return;
}
QObject::connect(
this->proxy,
&DBusNMConnectionSettingsProxy::Updated,
this,
&NMSettings::getSettings
);
this->bId.setBinding([this]() { return this->bSettings.value()["connection"]["id"].toString(); });
this->bUuid.setBinding([this]() {
return this->bSettings.value()["connection"]["uuid"].toString();
});
this->settingsProperties.setInterface(this->proxy);
this->settingsProperties.updateAllViaGetAll();
this->getSettings();
}
void NMSettings::getSettings() {
auto pending = this->proxy->GetSettings();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<NMSettingsMap> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to get settings for" << this->path() << ":" << reply.error().message();
} else {
auto settings = reply.value();
manualSettingDemarshall(settings);
this->bSettings = settings;
qCDebug(logNetworkManager) << "Settings map updated for" << this->path();
if (!this->mLoaded) {
emit this->loaded();
this->mLoaded = true;
}
};
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
QDBusPendingCallWatcher* NMSettings::updateSettings(
const NMSettingsMap& settingsToChange,
const NMSettingsMap& settingsToRemove
) {
auto settings = removeSettingsInMap(this->bSettings, settingsToRemove);
settings = mergeSettingsMaps(settings, settingsToChange);
auto pending = this->proxy->Update(settings);
auto* call = new QDBusPendingCallWatcher(pending, this);
return call;
}
void NMSettings::clearSecrets() {
auto pending = this->proxy->ClearSecrets();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to clear secrets for" << this->path() << ":" << reply.error().message();
} else {
qCDebug(logNetworkManager) << "Cleared secrets for" << this->path();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NMSettings::forget() {
auto pending = this->proxy->Delete();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to forget" << this->path() << ":" << reply.error().message();
} else {
qCDebug(logNetworkManager) << "Successfully deletion of" << this->path();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
QVariantMap NMSettings::read() {
QVariantMap result;
const auto& settings = this->bSettings.value();
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
QVariantMap group;
for (auto jt = it.value().constBegin(); jt != it.value().constEnd(); ++jt) {
group.insert(jt.key(), settingTypeToQml(jt.value()));
}
result.insert(it.key(), group);
}
return result;
}
void NMSettings::write(const QVariantMap& settings) {
NMSettingsMap changedSettings;
NMSettingsMap removedSettings;
QStringList failedSettings;
for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) {
if (!it.value().canConvert<QVariantMap>()) continue;
auto group = it.value().toMap();
QVariantMap toChange;
QVariantMap toRemove;
for (auto jt = group.constBegin(); jt != group.constEnd(); ++jt) {
if (jt.value().isNull()) {
toRemove.insert(jt.key(), QVariant());
} else {
auto converted = settingTypeFromQml(it.key(), jt.key(), jt.value());
if (!converted.isValid()) failedSettings.append(it.key() + "." + jt.key());
else toChange.insert(jt.key(), converted);
}
}
if (!toChange.isEmpty()) changedSettings.insert(it.key(), toChange);
if (!toRemove.isEmpty()) removedSettings.insert(it.key(), toRemove);
}
if (!failedSettings.isEmpty()) {
qCWarning(logNMSettings) << "A write to" << this
<< "has received bad types for the following settings:"
<< failedSettings.join(", ");
}
auto* call = this->updateSettings(changedSettings, removedSettings);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to update settings for" << this->path() << ":" << reply.error().message();
} else {
qCDebug(logNMSettings) << "Successful write to" << this;
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
bool NMSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
QString NMSettings::address() const { return this->proxy ? this->proxy->service() : QString(); }
QString NMSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings) {
auto saver = QDebugStateSaver(debug);
if (settings) {
debug.nospace() << "NMSettings(" << static_cast<const void*>(settings)
<< ", uuid=" << settings->uuid() << ")";
} else {
debug << "WifiNetwork(nullptr)";
}
return debug;
}

View file

@ -0,0 +1,82 @@
#pragma once
#include <qbytearray.h>
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qstringlist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "dbus_nm_connection_settings.h"
#include "dbus_types.hpp"
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
///! A NetworkManager connection settings profile.
class NMSettings: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("");
/// The human-readable unique identifier for the connection.
Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId);
/// A universally unique identifier for the connection.
Q_PROPERTY(QString uuid READ uuid NOTIFY uuidChanged BINDABLE bindableUuid);
public:
explicit NMSettings(const QString& path, QObject* parent = nullptr);
/// Clear all of the secrets belonging to the settings.
Q_INVOKABLE void clearSecrets();
/// Delete the settings.
Q_INVOKABLE void forget();
/// Update the connection with new settings and save the connection to disk.
/// Only changed fields need to be included.
/// Writing a setting to `null` will remove the setting or reset it to its default.
///
/// > [!NOTE] Secrets may be part of the update request,
/// > and will be either stored in persistent storage or sent to a Secret Agent for storage,
/// > depending on the flags associated with each secret.
Q_INVOKABLE void write(const QVariantMap& settings);
/// Get the settings map describing this network configuration.
///
/// > [!NOTE] This will never include any secrets required for connection to the network, as those are often protected.
Q_INVOKABLE QVariantMap read();
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] NMSettingsMap map() { return this->bSettings; }
QDBusPendingCallWatcher*
updateSettings(const NMSettingsMap& settingsToChange, const NMSettingsMap& settingsToRemove = {});
QBindable<QString> bindableId() { return &this->bId; }
[[nodiscard]] QString uuid() const { return this->bUuid; }
QBindable<QString> bindableUuid() { return &this->bUuid; }
signals:
void loaded();
void settingsChanged(NMSettingsMap settings);
void idChanged(QString id);
void uuidChanged(QString uuid);
private:
bool mLoaded = false;
void getSettings();
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, NMSettingsMap, bSettings, &NMSettings::settingsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bId, &NMSettings::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bUuid, &NMSettings::uuidChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettings, settingsProperties);
DBusNMConnectionSettingsProxy* proxy = nullptr;
};
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings);

View file

@ -1,27 +1,28 @@
#include "utils.hpp"
#include <cmath>
// We depend on non-std Linux extensions that ctime doesn't put in the global namespace
// NOLINTNEXTLINE(modernize-deprecated-headers)
#include <time.h>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdbusargument.h>
#include <qdbusservicewatcher.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtypes.h>
#include "../wifi.hpp"
#include "../enums.hpp"
#include "dbus_types.hpp"
#include "enums.hpp"
namespace qs::network {
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) {
const QVariantMap& security = settings.value("802-11-wireless-security");
if (security.isEmpty()) {
return WifiSecurityType::Open;
};
WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings) {
const QString mapName = "802-11-wireless-security";
if (!settings.contains(mapName)) return WifiSecurityType::Unknown;
const QVariantMap& security = settings.value(mapName);
if (security.isEmpty()) return WifiSecurityType::Open;
const QString keyMgmt = security["key-mgmt"].toString();
const QString authAlg = security["auth-alg"].toString();
@ -45,6 +46,8 @@ WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMa
return WifiSecurityType::Sae;
} else if (keyMgmt == "wpa-eap-suite-b-192") {
return WifiSecurityType::Wpa3SuiteB192;
} else if (keyMgmt == "owe") {
return WifiSecurityType::Owe;
}
return WifiSecurityType::Open;
}
@ -224,6 +227,304 @@ WifiSecurityType::Enum findBestWirelessSecurity(
return WifiSecurityType::Unknown;
}
NMSettingsMap mergeSettingsMaps(const NMSettingsMap& target, const NMSettingsMap& source) {
NMSettingsMap result = target;
for (auto iter = source.constBegin(); iter != source.constEnd(); ++iter) {
result[iter.key()].insert(iter.value());
}
return result;
}
NMSettingsMap removeSettingsInMap(const NMSettingsMap& target, const NMSettingsMap& toRemove) {
NMSettingsMap result = target;
for (auto iter = toRemove.constBegin(); iter != toRemove.constEnd(); ++iter) {
const QString& group = iter.key();
const QVariantMap& keysToRemove = iter.value();
if (!result.contains(group)) continue;
for (auto jt = keysToRemove.constBegin(); jt != keysToRemove.constEnd(); ++jt) {
result[group].remove(jt.key());
}
// Remove the group entirely if it's now empty
if (result[group].isEmpty()) {
result.remove(group);
}
}
return result;
}
// Some NMSettingsMap settings remain QDBusArguments after autodemarshalling.
// Manually demarshall these for any complex signature we have registered.
void manualSettingDemarshall(NMSettingsMap& map) {
auto demarshallValue = [](const QVariant& value) -> QVariant {
if (value.userType() != qMetaTypeId<QDBusArgument>()) {
return value;
}
auto arg = value.value<QDBusArgument>();
auto signature = arg.currentSignature();
if (signature == "ay") return QVariant::fromValue(qdbus_cast<QByteArray>(arg));
if (signature == "aay") return QVariant::fromValue(qdbus_cast<QList<QByteArray>>(arg));
if (signature == "au") return QVariant::fromValue(qdbus_cast<QList<quint32>>(arg));
if (signature == "aau") return QVariant::fromValue(qdbus_cast<QList<QList<quint32>>>(arg));
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;
};
for (auto it = map.begin(); it != map.end(); ++it)
for (auto jt = it.value().begin(); jt != it.value().end(); ++jt)
jt.value() = demarshallValue(jt.value());
}
// Some NMSettingsMap setting types can't be expressed in QML.
// Convert these settings to their correct type or return an invalid QVariant.
QVariant settingTypeFromQml(const QString& group, const QString& key, const QVariant& value) {
auto s = group + "." + key;
// QString -> QByteArray
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-3-ethernet.cloned-mac-address"
|| s == "802-3-ethernet.mac-address")
{
if (value.typeId() == QMetaType::QString) {
return value.toString().toUtf8();
}
if (value.typeId() == QMetaType::QByteArray) {
return value;
}
return QVariant();
}
// QVariantList -> QList<QByteArray>
if (s == "ipv6.dns") {
if (value.typeId() == QMetaType::QVariantList) {
QList<QByteArray> r;
for (const auto& v: value.toList()) {
if (v.typeId() == QMetaType::QString) {
r.append(v.toString().toUtf8());
} else {
r.append(v.toByteArray());
}
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QList<quint32>
if (s == "ipv4.dns") {
if (value.typeId() == QMetaType::QVariantList) {
QList<quint32> r;
for (const auto& v: value.toList()) {
r.append(v.value<quint32>());
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QList<QList<quint32>>
if (s == "ipv4.addresses" || s == "ipv4.routes") {
if (value.typeId() == QMetaType::QVariantList) {
QList<QList<quint32>> r;
for (const auto& v: value.toList()) {
if (v.typeId() != QMetaType::QVariantList) {
continue;
}
QList<quint32> inner;
for (const auto& u: v.toList()) {
inner.append(u.value<quint32>());
}
r.append(inner);
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QList<QVariantMap>
if (s == "ipv4.address-data" || s == "ipv4.route-data" || s == "ipv4.routing-rules"
|| s == "ipv6.address-data" || s == "ipv6.route-data" || s == "ipv6.routing-rules")
{
if (value.typeId() == QMetaType::QVariantList) {
QList<QVariantMap> r;
for (const auto& v: value.toList()) {
if (!v.canConvert<QVariantMap>()) {
continue;
}
r.append(v.toMap());
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QList<NMIPv6Address>
if (s == "ipv6.addresses") {
if (value.typeId() == QMetaType::QVariantList) {
QList<NMIPv6Address> r;
for (const auto& v: value.toList()) {
if (v.typeId() != QMetaType::QVariantList) {
continue;
}
auto fields = v.toList();
if (fields.size() != 3) {
continue;
}
const QByteArray address = fields[0].typeId() == QMetaType::QString
? fields[0].toString().toUtf8()
: fields[0].toByteArray();
const QByteArray gateway = fields[2].typeId() == QMetaType::QString
? fields[2].toString().toUtf8()
: fields[2].toByteArray();
r.append({.address = address, .prefix = fields[1].value<quint32>(), .gateway = gateway});
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QList<NMIPv6Route>
if (s == "ipv6.routes") {
if (value.typeId() == QMetaType::QVariantList) {
QList<NMIPv6Route> r;
for (const auto& v: value.toList()) {
if (v.typeId() != QMetaType::QVariantList) {
continue;
}
auto fields = v.toList();
if (fields.size() != 4) {
continue;
}
const QByteArray destination = fields[0].typeId() == QMetaType::QString
? fields[0].toString().toUtf8()
: fields[0].toByteArray();
const QByteArray nexthop = fields[2].typeId() == QMetaType::QString
? fields[2].toString().toUtf8()
: fields[2].toByteArray();
r.append(
{.destination = destination,
.prefix = fields[1].value<quint32>(),
.nexthop = nexthop,
.metric = fields[3].value<quint32>()}
);
}
return QVariant::fromValue(r);
}
return QVariant();
}
// QVariantList -> QStringList
if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search"
|| 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;
for (const auto& item: value.toList()) {
stringList.append(item.toString());
}
return stringList;
}
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();
if (std::isfinite(num) && num == std::trunc(num)) {
return QVariant::fromValue(static_cast<qint32>(num));
}
}
return value;
}
// Some NMSettingsMap setting types must be converted to a type that is supported by QML.
// Although QByteArrays can be represented in QML, we convert them to strings for convenience.
QVariant settingTypeToQml(const QVariant& value) {
// QByteArray -> QString
if (value.typeId() == QMetaType::QByteArray) {
return QString::fromUtf8(value.toByteArray());
}
// QList<QByteArray> -> QVariantList
if (value.userType() == qMetaTypeId<QList<QByteArray>>()) {
QVariantList out;
for (const auto& ba: value.value<QList<QByteArray>>()) {
out.append(QString::fromUtf8(ba));
}
return out;
}
// QList<NMIPv6Address> -> QVariantList
if (value.userType() == qMetaTypeId<QList<NMIPv6Address>>()) {
QVariantList out;
for (const auto& addr: value.value<QList<NMIPv6Address>>()) {
out.append(
QVariant::fromValue(
QVariantList {
QString::fromUtf8(addr.address),
addr.prefix,
QString::fromUtf8(addr.gateway),
}
)
);
}
return out;
}
// QList<NMIPv6Route> -> QVariantList
if (value.userType() == qMetaTypeId<QList<NMIPv6Route>>()) {
QVariantList out;
for (const auto& route: value.value<QList<NMIPv6Route>>()) {
out.append(
QVariant::fromValue(
QVariantList {
QString::fromUtf8(route.destination),
route.prefix,
QString::fromUtf8(route.nexthop),
route.metric,
}
)
);
}
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;
}
// NOLINTBEGIN
QDateTime clockBootTimeToDateTime(qint64 clockBootTime) {
clockid_t clkId = CLOCK_BOOTTIME;

View file

@ -3,15 +3,14 @@
#include <qcontainerfwd.h>
#include <qdbusservicewatcher.h>
#include <qobject.h>
#include <qqmlintegration.h>
#include "../wifi.hpp"
#include "../enums.hpp"
#include "dbus_types.hpp"
#include "enums.hpp"
namespace qs::network {
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings);
WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings);
bool deviceSupportsApCiphers(
NMWirelessCapabilities::Enum caps,
@ -40,6 +39,16 @@ WifiSecurityType::Enum findBestWirelessSecurity(
NM80211ApSecurityFlags::Enum apRsn
);
NMSettingsMap mergeSettingsMaps(const NMSettingsMap& target, const NMSettingsMap& source);
NMSettingsMap removeSettingsInMap(const NMSettingsMap& target, const NMSettingsMap& toRemove);
void manualSettingDemarshall(NMSettingsMap& map);
QVariant settingTypeFromQml(const QString& group, const QString& key, const QVariant& value);
QVariant settingTypeToQml(const QVariant& value);
QDateTime clockBootTimeToDateTime(qint64 clockBootTime);
} // namespace qs::network

101
src/network/nm/wired.cpp Normal file
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
src/network/nm/wired.hpp Normal file
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

View file

@ -1,6 +1,6 @@
#include "wireless.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
@ -14,17 +14,20 @@
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../network.hpp"
#include "../enums.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "connection.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wireless.h"
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
#include "utils.hpp"
namespace qs::network {
@ -34,137 +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::updateReferenceConnection() {
// If the network has no connections, the reference is nullptr.
if (this->mConnections.isEmpty()) {
this->mReferenceConn = nullptr;
this->bSecurity = WifiSecurityType::Unknown;
// Set security back to reference AP.
if (this->mReferenceAp) {
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
}
return;
};
// If the network has an active connection, use it as the reference.
if (this->mActiveConnection) {
auto* conn = this->mConnections.value(this->mActiveConnection->connection().path());
if (conn && conn != this->mReferenceConn) {
this->mReferenceConn = conn;
this->bSecurity.setBinding([conn]() { return conn->security(); });
}
return;
}
// Otherwise, choose the connection with the strongest security settings.
NMConnectionSettings* selectedConn = nullptr;
for (auto* conn: this->mConnections.values()) {
if (!selectedConn || conn->security() > selectedConn->security()) {
selectedConn = conn;
}
}
if (this->mReferenceConn != selectedConn) {
this->mReferenceConn = selectedConn;
this->bSecurity.setBinding([selectedConn]() { return selectedConn->security(); });
}
}
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->mReferenceConn) {
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->mConnections.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::addConnection(NMConnectionSettings* conn) {
if (this->mConnections.contains(conn->path())) return;
this->mConnections.insert(conn->path(), conn);
auto onDestroyed = [this, conn]() {
if (this->mConnections.take(conn->path())) {
this->updateReferenceConnection();
if (this->mConnections.isEmpty()) this->bKnown = false;
if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared();
}
};
// clang-format off
QObject::connect(conn, &NMConnectionSettings::securityChanged, this, &NMWirelessNetwork::updateReferenceConnection);
QObject::connect(conn, &NMConnectionSettings::destroyed, this, onDestroyed);
// clang-format on
this->bKnown = true;
this->updateReferenceConnection();
};
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->updateReferenceConnection();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceConnection();
};
void NMWirelessNetwork::forget() {
if (this->mConnections.isEmpty()) return;
for (auto* conn: this->mConnections.values()) {
conn->forget();
}
}
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
: NMDevice(path, parent)
, mScanTimer(this) {
@ -193,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() {
@ -200,11 +76,13 @@ void NMWirelessDevice::initWireless() {
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessDevice::onAccessPointAdded);
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessDevice::onAccessPointRemoved);
QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded);
QObject::connect(this, &NMWirelessDevice::connectionLoaded, this, &NMWirelessDevice::onConnectionLoaded);
QObject::connect(this, &NMWirelessDevice::settingsLoaded, this, &NMWirelessDevice::onSettingsLoaded);
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) {
@ -218,6 +96,7 @@ void NMWirelessDevice::onAccessPointRemoved(const QDBusObjectPath& path) {
<< "which is not registered.";
return;
}
qCDebug(logNetworkManager) << "Access point removed:" << path.path();
delete ap;
}
@ -233,28 +112,26 @@ void NMWirelessDevice::onAccessPointLoaded(NMAccessPoint* ap) {
}
}
void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) {
const ConnectionSettingsMap& settings = conn->settings();
void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
const NMSettingsMap& map = settings->map();
// Filter connections that aren't wireless or have missing settings
if (settings["connection"]["id"].toString().isEmpty()
|| settings["connection"]["uuid"].toString().isEmpty()
|| !settings.contains("802-11-wireless")
|| settings["802-11-wireless"]["ssid"].toString().isEmpty())
if (map["connection"]["id"].toString().isEmpty() || map["connection"]["uuid"].toString().isEmpty()
|| !map.contains("802-11-wireless") || map["802-11-wireless"]["ssid"].toString().isEmpty())
{
return;
}
const auto ssid = settings["802-11-wireless"]["ssid"].toString();
const auto mode = settings["802-11-wireless"]["mode"].toString();
const auto ssid = map["802-11-wireless"]["ssid"].toString();
const auto mode = map["802-11-wireless"]["mode"].toString();
if (mode == "infrastructure") {
auto* net = this->mNetworks.value(ssid);
if (!net) net = this->registerNetwork(ssid);
net->addConnection(conn);
net->addSettings(settings);
// Check for active connections that loaded before their respective connection settings
auto* active = this->activeConnection();
if (active && conn->path() == active->connection().path()) {
if (active && settings->path() == active->connection().path()) {
net->addActiveConnection(active);
}
}
@ -262,11 +139,11 @@ void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) {
}
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* conn: net->connections()) {
if (activeConnPath == conn->path()) {
for (auto* settings: net->settings()) {
if (activeConnPath == settings->path()) {
net->addActiveConnection(active);
return;
}
@ -334,6 +211,7 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
return;
}
qCDebug(logNetworkManager) << "Access point added:" << path;
this->mAccessPoints.insert(path, ap);
QObject::connect(
ap,
@ -354,89 +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);
// To avoid exposing outdated state to the frontend, filter the backend networks to only show
// the known or currently connected networks when the scanner is off.
this->NMDevice::bindNetwork(net);
auto visible = [this, net]() {
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
};
auto onVisibilityChanged = [this, net](bool visible) {
visible ? this->registerFrontendNetwork(net) : this->removeFrontendNetwork(net);
};
net->bindableVisible().setBinding(visible);
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
QObject::connect(net, &NMWirelessNetwork::visibilityChanged, this, onVisibilityChanged);
this->mNetworks.insert(ssid, net);
if (net->visible()) 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->bindableNmReason().setBinding([net]() { return net->reason(); });
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
frontendNet->bindableState().setBinding([net]() {
return static_cast<NetworkState::Enum>(net->state());
});
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
if (net->referenceConnection()) {
emit this->activateConnection(
QDBusObjectPath(net->referenceConnection()->path()),
QDBusObjectPath(this->path())
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
ConnectionSettingsMap(),
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
}
});
QObject::connect(
frontendNet,
&WifiNetwork::requestDisconnect,
this,
&NMWirelessDevice::disconnect
);
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
this->mFrontendNetworks.insert(ssid, frontendNet);
emit this->networkAdded(frontendNet);
}
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
if (frontendNet) {
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());
}

View file

@ -9,10 +9,12 @@
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "connection.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wireless.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
template <>
@ -32,70 +34,9 @@ struct DBusDataTransform<QDateTime> {
} // namespace qs::dbus
namespace qs::network {
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMConnectionSetting objects.
class NMWirelessNetwork: public QObject {
Q_OBJECT;
public:
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
void addConnection(NMConnectionSettings* conn);
void addActiveConnection(NMActiveConnection* active);
void forget();
[[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; };
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; };
[[nodiscard]] NMConnectionSettings* referenceConnection() const { return this->mReferenceConn; };
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); };
[[nodiscard]] QList<NMConnectionSettings*> connections() const {
return this->mConnections.values();
}
[[nodiscard]] QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; };
[[nodiscard]] QBindable<bool> bindableVisible() { return &this->bVisible; };
[[nodiscard]] bool visible() const { return this->bVisible; };
signals:
void disappeared();
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 capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void activeApPathChanged(QString path);
private:
void updateReferenceAp();
void updateReferenceConnection();
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMConnectionSettings*> mConnections;
NMAccessPoint* mReferenceAp = nullptr;
NMConnectionSettings* mReferenceConn = 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, 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;
@ -103,16 +44,15 @@ public:
explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const override;
[[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; };
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; };
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; };
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; };
[[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; }
[[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);
@ -123,24 +63,23 @@ private slots:
void onAccessPointAdded(const QDBusObjectPath& path);
void onAccessPointRemoved(const QDBusObjectPath& path);
void onAccessPointLoaded(NMAccessPoint* ap);
void onConnectionLoaded(NMConnectionSettings* conn);
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
void onScanTimeout();
void onScanningChanged(bool scanning);
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
src/network/qml.cpp Normal file
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
src/network/qml.hpp Normal file
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

View file

@ -5,6 +5,309 @@ import Quickshell
import Quickshell.Widgets
import Quickshell.Networking
Scope {
Component {
id: editorComponent
FloatingWindow {
id: editorWindow
required property var nmSettings
color: contentItem.palette.window
Component.onCompleted: editorArea.text = JSON.stringify(nmSettings.read(), null, 2)
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
Label {
text: "Editing " + nmSettings?.id + " (" + nmSettings?.uuid + ")"
font.bold: true
font.pointSize: 12
}
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
TextArea {
id: editorArea
wrapMode: TextEdit.Wrap
selectByMouse: true
}
}
RowLayout {
Layout.fillWidth: true
Label {
id: statusLabel
Layout.fillWidth: true
color: palette.placeholderText
}
Button {
text: "Reload"
onClicked: {
editorArea.text = JSON.stringify(editorWindow.nmSettings.read(), null, 2);
statusLabel.text = "Reloaded";
}
}
Button {
text: "Save"
onClicked: {
try {
const parsed = JSON.parse(editorArea.text);
nmSettings.write(parsed);
statusLabel.text = "Saved";
} catch (e) {
statusLabel.text = "Parse error: " + e.message;
}
}
}
Button {
text: "Close"
onClicked: {
editorArea.focus = false;
editorWindow.destroy();
}
}
}
}
}
}
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
@ -12,13 +315,46 @@ FloatingWindow {
anchors.fill: parent
anchors.margins: 5
ColumnLayout {
Label {
text: `Networking (${NetworkBackendType.toString(Networking.backend)} backend)`
font.bold: true
font.pointSize: 12
}
RowLayout {
Label {
text: `Connectivity`
font.bold: true
}
Label {
text: `${NetworkConnectivity.toString(Networking.connectivity)}`
visible: Networking.canCheckConnectivity
}
Button {
text: "Re-check"
visible: Networking.canCheckConnectivity && Networking.connectivityCheckEnabled
onClicked: Networking.checkConnectivity()
}
CheckBox {
text: "Checking enabled"
checked: Networking.connectivityCheckEnabled
onClicked: Networking.connectivityCheckEnabled = !Networking.connectivityCheckEnabled
visible: Networking.canCheckConnectivity
}
CheckBox {
enabled: false
text: "Supported"
checked: Networking.canCheckConnectivity
}
}
}
Column {
Layout.fillWidth: true
RowLayout {
Label {
text: "WiFi"
font.bold: true
font.pointSize: 12
}
CheckBox {
text: "Software"
@ -38,115 +374,11 @@ FloatingWindow {
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)})` }
}
RowLayout {
Label {
text: DeviceConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Label {
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting)
text: `(${NMDeviceState.toString(modelData.nmState)})`
}
Button {
visible: modelData.state == DeviceConnectionState.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: {
if (modelData.type !== DeviceType.Wifi) return []
return [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected
}
return b.signalStrength - a.signalStrength
})
}
WrapperRectangle {
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
}
}
Label {
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None)
text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}`
}
}
RowLayout {
Layout.alignment: Qt.AlignRight
Button {
text: "Connect"
onClicked: modelData.connect()
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
}
}
}
delegate: Component {
Loader {
required property var modelData
width: ListView.view.width
sourceComponent: deviceDelegate
}
}
}

View file

@ -6,99 +6,36 @@
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logWifi, "quickshell.network.wifi", QtWarningMsg);
} // namespace
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
switch (type) {
case Unknown: return QStringLiteral("Unknown");
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
case Sae: return QStringLiteral("WPA3");
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
case Wpa2Psk: return QStringLiteral("WPA2");
case WpaEap: return QStringLiteral("WPA Enterprise");
case WpaPsk: return QStringLiteral("WPA");
case StaticWep: return QStringLiteral("WEP");
case DynamicWep: return QStringLiteral("Dynamic WEP");
case Leap: return QStringLiteral("LEAP");
case Owe: return QStringLiteral("OWE");
case Open: return QStringLiteral("Open");
default: return QStringLiteral("Unknown");
}
QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg);
}
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
switch (mode) {
case Unknown: return QStringLiteral("Unknown");
case AdHoc: return QStringLiteral("Ad-Hoc");
case Station: return QStringLiteral("Station");
case AccessPoint: return QStringLiteral("Access Point");
case Mesh: return QStringLiteral("Mesh");
default: return QStringLiteral("Unknown");
};
}
WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent)
: Network(std::move(ssid), device, parent) {};
QString NMConnectionStateReason::toString(NMConnectionStateReason::Enum reason) {
switch (reason) {
case Unknown: return QStringLiteral("Unknown");
case None: return QStringLiteral("No reason");
case UserDisconnected: return QStringLiteral("User disconnection");
case DeviceDisconnected:
return QStringLiteral("The device the connection was using was disconnected.");
case ServiceStopped:
return QStringLiteral("The service providing the VPN connection was stopped.");
case IpConfigInvalid:
return QStringLiteral("The IP config of the active connection was invalid.");
case ConnectTimeout:
return QStringLiteral("The connection attempt to the VPN service timed out.");
case ServiceStartTimeout:
return QStringLiteral(
"A timeout occurred while starting the service providing the VPN connection."
);
case ServiceStartFailed:
return QStringLiteral("Starting the service providing the VPN connection failed.");
case NoSecrets: return QStringLiteral("Necessary secrets for the connection were not provided.");
case LoginFailed: return QStringLiteral("Authentication to the server failed.");
case ConnectionRemoved:
return QStringLiteral("Necessary secrets for the connection were not provided.");
case DependencyFailed:
return QStringLiteral("Master connection of this connection failed to activate.");
case DeviceRealizeFailed: return QStringLiteral("Could not create the software device link.");
case DeviceRemoved: return QStringLiteral("The device this connection depended on disappeared.");
default: return QStringLiteral("Unknown");
};
};
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
void WifiNetwork::connect() {
void WifiNetwork::connectWithPsk(const QString& psk) {
if (this->bConnected) {
qCCritical(logWifi) << this << "is already connected.";
qCCritical(logWifiNetwork) << this << "is already connected.";
return;
}
this->requestConnect();
}
void WifiNetwork::disconnect() {
if (!this->bConnected) {
qCCritical(logWifi) << this << "is not currently connected";
if (this->bSecurity != WifiSecurityType::WpaPsk && this->bSecurity != WifiSecurityType::Wpa2Psk
&& this->bSecurity != WifiSecurityType::Sae)
{
qCCritical(logWifiNetwork) << this << "has the wrong security type for a PSK.";
return;
}
this->requestDisconnect();
emit this->requestConnectWithPsk(psk);
}
void WifiNetwork::forget() { this->requestForget(); }
WifiDevice::WifiDevice(QObject* parent): NetworkDevice(DeviceType::Wifi, parent) {};
void WifiDevice::setScannerEnabled(bool enabled) {
@ -106,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) {

View file

@ -6,90 +6,14 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/model.hpp"
#include "../core/doc.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! The security type of a wifi network.
class WifiSecurityType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Wpa3SuiteB192 = 0,
Sae = 1,
Wpa2Eap = 2,
Wpa2Psk = 3,
WpaEap = 4,
WpaPsk = 5,
StaticWep = 6,
DynamicWep = 7,
Leap = 8,
Owe = 9,
Open = 10,
Unknown = 11,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
};
///! The 802.11 mode of a wifi device.
class WifiDeviceMode: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// The device is part of an Ad-Hoc network without a central access point.
AdHoc = 0,
/// The device is a station that can connect to networks.
Station = 1,
/// The device is a local hotspot/access point.
AccessPoint = 2,
/// The device is an 802.11s mesh point.
Mesh = 3,
/// The device mode is unknown.
Unknown = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
};
///! NetworkManager-specific reason for a WifiNetworks connection state.
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason.
class NMConnectionStateReason: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
None = 1,
UserDisconnected = 2,
DeviceDisconnected = 3,
ServiceStopped = 4,
IpConfigInvalid = 5,
ConnectTimeout = 6,
ServiceStartTimeout = 7,
ServiceStartFailed = 8,
NoSecrets = 9,
LoginFailed = 10,
ConnectionRemoved = 11,
DependencyFailed = 12,
DeviceRealizeFailed = 13,
DeviceRemoved = 14
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NMConnectionStateReason::Enum reason);
};
///! An available wifi network.
///! WiFi subtype of @@Network.
class WifiNetwork: public Network {
Q_OBJECT;
QML_ELEMENT;
@ -97,60 +21,45 @@ class WifiNetwork: public Network {
// clang-format off
/// The current signal strength of the network, from 0.0 to 1.0.
Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength);
/// True if the wifi network has known connection settings saved.
Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown);
/// The security type of the wifi network.
Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity);
/// A specific reason for the connection state when the backend is NetworkManager.
Q_PROPERTY(NMConnectionStateReason::Enum nmReason READ default NOTIFY nmReasonChanged BINDABLE bindableNmReason);
// clang-format on
public:
explicit WifiNetwork(QString ssid, QObject* parent = nullptr);
/// Attempt to connect to the wifi network.
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`.
///
/// > [!WARNING] Quickshell does not yet provide a NetworkManager authentication agent,
/// > meaning another agent will need to be active to enter passwords for unsaved networks.
Q_INVOKABLE void connect();
/// Disconnect from the wifi network.
Q_INVOKABLE void disconnect();
/// Forget all connection settings for this wifi network.
Q_INVOKABLE void forget();
/// The networking backend may store the PSK for future use with @@Network.connect().
/// As such, calling that function first is recommended to avoid having to show a
/// prompt if not required.
///
/// > [!NOTE] PSKs should only be provided when the @@security is one of
/// > `WpaPsk`, `Wpa2Psk`, or `Sae`.
Q_INVOKABLE void connectWithPsk(const QString& psk);
QBindable<qreal> bindableSignalStrength() { return &this->bSignalStrength; }
QBindable<bool> bindableKnown() { return &this->bKnown; }
QBindable<NMConnectionStateReason::Enum> bindableNmReason() { return &this->bNmReason; }
QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; }
signals:
void requestConnect();
void requestDisconnect();
void requestForget();
QSDOC_HIDE void requestConnectWithPsk(QString psk);
void signalStrengthChanged();
void knownChanged();
void securityChanged();
void nmReasonChanged();
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, qreal, bSignalStrength, &WifiNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bKnown, &WifiNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, NMConnectionStateReason::Enum, bNmReason, &WifiNetwork::nmReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, WifiSecurityType::Enum, bSecurity, &WifiNetwork::securityChanged);
// clang-format on
};
///! Wireless variant of a NetworkDevice.
///! WiFi variant of a @@NetworkDevice.
class WifiDevice: public NetworkDevice {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("");
// clang-format off
/// A list of this available and 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);
@ -161,12 +70,8 @@ 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; };
QBindable<bool> bindableScannerEnabled() { return &this->bScannerEnabled; }
[[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; }
void setScannerEnabled(bool enabled);
QBindable<WifiDeviceMode::Enum> bindableMode() { return &this->bMode; }
@ -175,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
src/network/wired.cpp Normal file
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
src/network/wired.hpp Normal file
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

View file

@ -214,13 +214,24 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) {
qCInfo(logDefaults) << "Default sink changed to" << node;
if (this->mDefaultSink != nullptr) {
QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr);
// Targeted disconnect is used because this can also be the default configured sink.
QObject::disconnect(
this->mDefaultSink,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultSinkDestroyed
);
}
this->mDefaultSink = node;
if (node != nullptr) {
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSinkDestroyed);
QObject::connect(
node,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultSinkDestroyed
);
}
emit this->defaultSinkChanged();
@ -244,13 +255,24 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) {
qCInfo(logDefaults) << "Default source changed to" << node;
if (this->mDefaultSource != nullptr) {
QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr);
// Targeted disconnect is used because this can also be the default configured source.
QObject::disconnect(
this->mDefaultSource,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultSourceDestroyed
);
}
this->mDefaultSource = node;
if (node != nullptr) {
QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSourceDestroyed);
QObject::connect(
node,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultSourceDestroyed
);
}
emit this->defaultSourceChanged();
@ -274,7 +296,13 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
qCInfo(logDefaults) << "Default configured sink changed to" << node;
if (this->mDefaultConfiguredSink != nullptr) {
QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr);
// Targeted disconnect is used because this can also be the default sink.
QObject::disconnect(
this->mDefaultConfiguredSink,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultConfiguredSinkDestroyed
);
}
this->mDefaultConfiguredSink = node;
@ -282,7 +310,7 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
if (node != nullptr) {
QObject::connect(
node,
&QObject::destroyed,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultConfiguredSinkDestroyed
);
@ -309,7 +337,13 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
qCInfo(logDefaults) << "Default configured source changed to" << node;
if (this->mDefaultConfiguredSource != nullptr) {
QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr);
// Targeted disconnect is used because this can also be the default source.
QObject::disconnect(
this->mDefaultConfiguredSource,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultConfiguredSourceDestroyed
);
}
this->mDefaultConfiguredSource = node;
@ -317,7 +351,7 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
if (node != nullptr) {
QObject::connect(
node,
&QObject::destroyed,
&PwBindableObject::destroying,
this,
&PwDefaultTracker::onDefaultConfiguredSourceDestroyed
);

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

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.

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

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

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

View file

@ -1,6 +1,7 @@
#include "dmabuf.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
@ -9,6 +10,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GL/gl.h>
#include <fcntl.h>
#include <gbm.h>
@ -17,6 +19,7 @@
#include <private/qrhivulkan_p.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qdir.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@ -24,7 +27,7 @@
#include <qopenglcontext_platform.h>
#include <qpair.h>
#include <qquickwindow.h>
#include <qscopedpointer.h>
#include <qscopeguard.h>
#include <qsgrendererinterface.h>
#include <qsgtexture_platform.h>
#include <qtypes.h>
@ -33,6 +36,8 @@
#include <qwayland-linux-dmabuf-v1.h>
#include <qwaylandclientextension.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <vulkan/vulkan_core.h>
@ -54,23 +59,33 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg)
LinuxDmabufManager* MANAGER = nullptr; // NOLINT
struct SupportedDrmFormat {
uint32_t drmFormat;
VkFormat vkFormat;
};
// XRGB/ARGB intentionally map to the same VK format.
constexpr std::array<SupportedDrmFormat, 11> SUPPORTED_VK_FORMATS = {{
{.drmFormat = DRM_FORMAT_ARGB8888, .vkFormat = VK_FORMAT_B8G8R8A8_UNORM},
{.drmFormat = DRM_FORMAT_XRGB8888, .vkFormat = VK_FORMAT_B8G8R8A8_UNORM},
{.drmFormat = DRM_FORMAT_ABGR8888, .vkFormat = VK_FORMAT_R8G8B8A8_UNORM},
{.drmFormat = DRM_FORMAT_XBGR8888, .vkFormat = VK_FORMAT_R8G8B8A8_UNORM},
{.drmFormat = DRM_FORMAT_ARGB2101010, .vkFormat = VK_FORMAT_A2R10G10B10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_XRGB2101010, .vkFormat = VK_FORMAT_A2R10G10B10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_ABGR2101010, .vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_XBGR2101010, .vkFormat = VK_FORMAT_A2B10G10R10_UNORM_PACK32},
{.drmFormat = DRM_FORMAT_ABGR16161616F, .vkFormat = VK_FORMAT_R16G16B16A16_SFLOAT},
{.drmFormat = DRM_FORMAT_RGB565, .vkFormat = VK_FORMAT_R5G6B5_UNORM_PACK16},
{.drmFormat = DRM_FORMAT_BGR565, .vkFormat = VK_FORMAT_B5G6R5_UNORM_PACK16},
}};
VkFormat drmFormatToVkFormat(uint32_t drmFormat) {
// NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format
switch (drmFormat) {
case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM;
case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM;
case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT;
case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16;
case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16;
default: return VK_FORMAT_UNDEFINED;
}
// NOLINTEND(bugprone-branch-clone)
const auto* it = std::ranges::find_if(SUPPORTED_VK_FORMATS, [&](const SupportedDrmFormat& f) {
return f.drmFormat == drmFormat;
});
if (it == SUPPORTED_VK_FORMATS.end()) return VK_FORMAT_UNDEFINED;
return it->vkFormat;
}
bool drmFormatHasAlpha(uint32_t drmFormat) {
@ -84,6 +99,13 @@ bool drmFormatHasAlpha(uint32_t drmFormat) {
}
}
template <typename T>
T eglProc(const char* name) {
auto* proc = reinterpret_cast<T>(eglGetProcAddress(name));
if (!proc) qCCritical(logDmabuf) << "Failed to look up EGL proc:" << name;
return proc;
}
} // namespace
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) {
@ -111,10 +133,13 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
return debug;
}
GbmDeviceHandle::~GbmDeviceHandle() {
if (this->device) {
MANAGER->unrefGbmDevice(this->device);
}
GbmDevice::~GbmDevice() {
qCDebug(logDmabuf) << "Destroying GBM device on render node" << this->renderNode.c_str();
auto fd = gbm_device_get_fd(this->device);
gbm_device_destroy(this->device);
close(fd);
MANAGER->gbmDevices.removeIf([](const std::weak_ptr<GbmDevice>& d) { return d.expired(); });
}
// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause
@ -291,52 +316,311 @@ LinuxDmabufManager::LinuxDmabufManager(WlBufferManagerPrivate* manager)
}
}
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
bool LinuxDmabufManager::initRenderFormats(QQuickWindow* window) {
auto* ri = window->rendererInterface();
if (ri->graphicsApi() == QSGRendererInterface::Vulkan) {
return this->initRenderFormatsVk(window);
} else {
return this->initRenderFormatsGl(window);
}
}
GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
struct DrmFree {
static void cleanup(drmDevice* d) { drmFreeDevice(&d); }
bool LinuxDmabufManager::initRenderFormatsGl(QQuickWindow* window) {
auto* ri = window->rendererInterface();
auto* context = static_cast<QOpenGLContext*>(
ri->getResource(window, QSGRendererInterface::OpenGLContextResource)
);
if (!context) {
qCCritical(logDmabuf) << "Failed to query render formats: No GL context.";
return false;
}
auto* qEglContext = context->nativeInterface<QNativeInterface::QEGLContext>();
if (!qEglContext) {
qCCritical(logDmabuf) << "Failed to query render formats: No EGL context.";
return false;
}
auto* display = qEglContext->display();
static auto* eglQueryDisplayAttribEXT =
eglProc<PFNEGLQUERYDISPLAYATTRIBEXTPROC>("eglQueryDisplayAttribEXT");
static auto* eglQueryDeviceStringEXT =
eglProc<PFNEGLQUERYDEVICESTRINGEXTPROC>("eglQueryDeviceStringEXT");
static auto* eglQueryDmaBufFormatsEXT =
eglProc<PFNEGLQUERYDMABUFFORMATSEXTPROC>("eglQueryDmaBufFormatsEXT");
static auto* eglQueryDmaBufModifiersEXT =
eglProc<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>("eglQueryDmaBufModifiersEXT");
if (!eglQueryDisplayAttribEXT || !eglQueryDeviceStringEXT || !eglQueryDmaBufFormatsEXT
|| !eglQueryDmaBufModifiersEXT)
{
return false;
}
EGLAttrib deviceAttrib = 0;
if (!eglQueryDisplayAttribEXT(display, EGL_DEVICE_EXT, &deviceAttrib)) {
qCCritical(logDmabuf) << "Failed to find render device: device display attrib missing.";
return false;
}
auto* dev = reinterpret_cast<EGLDeviceEXT>(deviceAttrib); // NOLINT
if (const auto* renderNode = eglQueryDeviceStringEXT(dev, EGL_DRM_RENDER_NODE_FILE_EXT)) {
this->renderNode = renderNode;
} else if (const auto* primaryNode = eglQueryDeviceStringEXT(dev, EGL_DRM_DEVICE_FILE_EXT)) {
this->renderNode = primaryNode;
} else {
qCCritical(logDmabuf) << "Failed to find render device: no render or primary node found.";
return false;
}
qCDebug(logDmabuf) << "Found render node:" << this->renderNode;
EGLint numFormats = 0;
if (!eglQueryDmaBufFormatsEXT(display, 0, nullptr, &numFormats)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufFormatsEXT failed.";
return false;
}
if (numFormats == 0) {
qCCritical(logDmabuf) << "Failed to query render formats: zero formats.";
return false;
}
auto formats = std::vector<EGLint>(numFormats);
if (!eglQueryDmaBufFormatsEXT(display, numFormats, formats.data(), &numFormats)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufFormatsEXT failed.";
return false;
}
qCDebug(logDmabuf) << "Render formats:";
for (auto format: formats) {
qCDebug(logDmabuf) << " Format" << FourCCStr(format);
qCDebug(logDmabuf) << " Implicit Modifier";
EGLint numModifiers = 0;
if (!eglQueryDmaBufModifiersEXT(display, format, 0, nullptr, nullptr, &numModifiers)) {
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufModifiersEXT failed.";
return false;
}
auto modifiers = std::vector<EGLuint64KHR>(numModifiers);
auto externalOnly = std::vector<EGLBoolean>(numModifiers);
if (!eglQueryDmaBufModifiersEXT(
display,
format,
numModifiers,
modifiers.data(),
externalOnly.data(),
&numModifiers
))
{
qCCritical(logDmabuf) << "Failed to query render formats: eglQueryDmaBufModifiersEXT failed.";
return false;
}
LinuxDmabufModifiers mods;
for (size_t i = 0; i != modifiers.size(); ++i) {
// External-only modifiers are required for some MGPU cases (usually involving nvidia).
// The work has not been done to support importing these, so they'll fall back to SHM.
auto external = externalOnly[i] == EGL_TRUE;
auto modifier = modifiers[i];
if (external) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier)
<< "(external / skipped)";
} else {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier);
mods.modifiers.push(modifier);
}
}
// It seems like EGL always supports implicit modifiers, however they don't work across GPUs so
// renderdev must be checked to match the tranche.
mods.implicit = true;
this->renderFormats.formats.push(qMakePair(format, mods));
}
return true;
}
bool LinuxDmabufManager::initRenderFormatsVk(QQuickWindow* window) {
auto* ri = window->rendererInterface();
auto* vkInst = window->vulkanInstance();
if (!vkInst) {
qCCritical(logDmabuf) << "Failed to query render formats: no QVulkanInstance.";
return false;
}
auto* vkDevicePtr =
static_cast<VkDevice*>(ri->getResource(window, QSGRendererInterface::DeviceResource));
auto* vkPhysDevicePtr = static_cast<VkPhysicalDevice*>(
ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource)
);
if (!vkDevicePtr || !vkPhysDevicePtr) {
qCCritical(logDmabuf) << "Failed to query render formats: could not get Vulkan device.";
return false;
}
auto* physDevice = *vkPhysDevicePtr;
auto* instFuncs = vkInst->functions();
auto drmProps = VkPhysicalDeviceDrmPropertiesEXT {};
drmProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRM_PROPERTIES_EXT;
auto props2 = VkPhysicalDeviceProperties2 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
.pNext = &drmProps,
.properties = {}
};
std::string renderNodeStorage;
std::string* renderNode = nullptr;
instFuncs->vkGetPhysicalDeviceProperties2(physDevice, &props2);
auto sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.handle == handle;
});
auto devDri = QDir("/dev/dri");
auto entries = devDri.entryList(QDir::System);
auto findNode = [&](uint32_t maj, uint32_t min) -> QString {
for (const QString& file: entries) {
auto path = devDri.filePath(file);
struct stat st = {};
if (::stat(path.toLocal8Bit().constData(), &st) != 0) continue;
if (major(st.st_rdev) == maj && minor(st.st_rdev) == min) {
return path;
}
}
return {};
};
if (sharedDevice != this->gbmDevices.end()) {
renderNode = &sharedDevice->renderNode;
QString node;
if (drmProps.hasRender) node = findNode(drmProps.renderMajor, drmProps.renderMinor);
else if (drmProps.hasPrimary) {
node = findNode(drmProps.primaryMajor, drmProps.primaryMinor);
}
if (!node.isEmpty()) {
this->renderNode = node.toLocal8Bit();
qCDebug(logDmabuf) << "Found render node:" << this->renderNode;
}
qCDebug(logDmabuf) << "Render formats:";
for (const auto& format: SUPPORTED_VK_FORMATS) {
auto modList = VkDrmFormatModifierPropertiesListEXT {
.sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT,
.pNext = nullptr,
.drmFormatModifierCount = 0,
.pDrmFormatModifierProperties = nullptr,
};
auto props2 = VkFormatProperties2 {
.sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2,
.pNext = &modList,
.formatProperties = {},
};
instFuncs->vkGetPhysicalDeviceFormatProperties2(physDevice, format.vkFormat, &props2);
if (modList.drmFormatModifierCount == 0) continue;
auto vkMods = std::vector<VkDrmFormatModifierPropertiesEXT>(modList.drmFormatModifierCount);
modList.pDrmFormatModifierProperties = vkMods.data();
instFuncs->vkGetPhysicalDeviceFormatProperties2(physDevice, format.vkFormat, &props2);
qCDebug(logDmabuf) << " Format" << FourCCStr(format.drmFormat);
LinuxDmabufModifiers mods;
for (const auto& m: vkMods) {
auto modInfo = VkPhysicalDeviceImageDrmFormatModifierInfoEXT {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
.pNext = nullptr,
.drmFormatModifier = m.drmFormatModifier,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
auto info = VkPhysicalDeviceImageFormatInfo2 {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
.pNext = &modInfo,
.format = format.vkFormat,
.type = VK_IMAGE_TYPE_2D,
.tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT,
.usage = VK_IMAGE_USAGE_SAMPLED_BIT,
.flags = {},
};
auto props = VkImageFormatProperties2 {
.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2,
.pNext = nullptr,
.imageFormatProperties = {},
};
auto r = instFuncs->vkGetPhysicalDeviceImageFormatProperties2(physDevice, &info, &props);
if (r == VK_SUCCESS) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier);
mods.modifiers.push(m.drmFormatModifier);
} else if (r == VK_ERROR_FORMAT_NOT_SUPPORTED) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier)
<< "(not usable)";
} else {
drmDevice* drmDevPtr = nullptr;
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDevPtr); error != 0) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(m.drmFormatModifier)
<< "(unknown error)";
}
}
if (mods.modifiers.isEmpty()) continue;
// We don't enable implicit modifiers because they seem to be broken in our current impl.
this->renderFormats.formats.push(qMakePair(format.drmFormat, mods));
}
return true;
}
void LinuxDmabufManager::feedbackDone() { this->manager->dmabufReady(); }
std::shared_ptr<GbmDevice> LinuxDmabufManager::getGbmDevice(dev_t handle) {
for (const auto& weak: this->gbmDevices) {
auto shared = weak.lock();
if (shared->handle == handle) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << shared->renderNode.c_str();
return shared;
}
}
drmDevice* drmDev = nullptr;
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDev); error != 0) {
qCWarning(logDmabuf) << "Failed to get drm device information from handle:"
<< qt_error_string(error);
return nullptr;
}
auto drmDev = QScopedPointer<drmDevice, DrmFree>(drmDevPtr);
auto freeDrmDev = qScopeGuard([&] { drmFreeDevice(&drmDev); });
if (!(drmDev->available_nodes & (1 << DRM_NODE_RENDER))) {
std::string renderNodeStorage;
if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) {
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
} else if (drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)) {
renderNodeStorage = drmDev->nodes[DRM_NODE_PRIMARY]; // NOLINT
} else {
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
return nullptr;
}
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
renderNode = &renderNodeStorage;
sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.renderNode == renderNodeStorage;
});
for (const auto& weak: this->gbmDevices) {
auto shared = weak.lock();
if (shared->renderNode == renderNodeStorage) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << renderNodeStorage.c_str();
return shared;
}
}
if (sharedDevice != this->gbmDevices.end()) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << *renderNode;
++sharedDevice->refcount;
return sharedDevice->device;
} else {
auto fd = open(renderNode->c_str(), O_RDWR | O_CLOEXEC);
auto fd = open(renderNodeStorage.c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
qCDebug(logDmabuf) << "Could not open render node" << *renderNode << ":"
qCDebug(logDmabuf) << "Could not open render node" << renderNodeStorage.c_str() << ":"
<< qt_error_string(fd);
return nullptr;
}
@ -344,53 +628,17 @@ GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
auto* device = gbm_create_device(fd);
if (!device) {
qCDebug(logDmabuf) << "Failed to create GBM device from render node" << *renderNode;
qCDebug(logDmabuf) << "Failed to create GBM device from render node"
<< renderNodeStorage.c_str();
close(fd);
return nullptr;
}
qCDebug(logDmabuf) << "Created GBM device on render node" << *renderNode;
qCDebug(logDmabuf) << "Created GBM device on render node" << renderNodeStorage.c_str();
this->gbmDevices.push_back({
.handle = handle,
.renderNode = std::move(renderNodeStorage),
.device = device,
.refcount = 1,
});
return device;
}
}
void LinuxDmabufManager::unrefGbmDevice(gbm_device* device) {
auto iter = std::ranges::find_if(this->gbmDevices, [device](const SharedGbmDevice& d) {
return d.device == device;
});
if (iter == this->gbmDevices.end()) return;
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
if (--iter->refcount == 0) {
auto fd = gbm_device_get_fd(iter->device);
gbm_device_destroy(iter->device);
close(fd);
this->gbmDevices.erase(iter);
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
}
}
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
if (!handle) return GbmDeviceHandle();
auto iter = std::ranges::find_if(this->gbmDevices, [&handle](const SharedGbmDevice& d) {
return d.device == *handle;
});
if (iter == this->gbmDevices.end()) return GbmDeviceHandle();
qCDebug(logDmabuf) << "Duplicated GBM device handle" << *handle;
++iter->refcount;
return GbmDeviceHandle(*handle);
auto shared = std::make_shared<GbmDevice>(handle, std::move(renderNodeStorage), device);
this->gbmDevices.push_back(shared);
return shared;
}
WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
@ -400,17 +648,68 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
}
LinuxDmabufFormatSelection formats;
for (const auto& format: request.dmabuf.formats) {
if (!format.modifiers.isEmpty()) {
formats.formats.push(
qMakePair(format.format, LinuxDmabufModifiers {.modifiers = format.modifiers})
);
} else {
for (const auto& trancheFormat: tranche.formats.formats) {
if (trancheFormat.first == format.format) {
formats.formats.push(trancheFormat);
for (const auto& requestFormat: request.dmabuf.formats) {
for (const auto& [renderFormat, renderFormatModifiers]: this->renderFormats.formats) {
if (renderFormat != requestFormat.format) continue;
if (!requestFormat.modifiers.isEmpty()) {
LinuxDmabufModifiers mods;
mods.implicit = requestFormat.implicit && renderFormatModifiers.implicit;
for (auto mod: requestFormat.modifiers) {
for (auto renderMod: renderFormatModifiers.modifiers) {
if (mod != renderMod) continue;
mods.modifiers.push(mod);
break;
}
}
if (mods.implicit || !mods.modifiers.isEmpty()) {
formats.formats.push(qMakePair(requestFormat.format, mods));
}
} else {
for (const auto& [trancheFormat, trancheMods]: tranche.formats.formats) {
if (trancheFormat != requestFormat.format) continue;
LinuxDmabufModifiers mods;
mods.implicit = trancheMods.implicit && renderFormatModifiers.implicit;
for (auto mod: trancheMods.modifiers) {
for (auto renderMod: renderFormatModifiers.modifiers) {
if (mod != renderMod) continue;
mods.modifiers.push(mod);
break;
}
}
if (mods.implicit || !mods.modifiers.isEmpty()) {
formats.formats.push(qMakePair(trancheFormat, mods));
}
break;
}
}
break;
}
}
if (logDmabuf().isDebugEnabled()) {
qCDebug(logDmabuf) << "Usable formats on device" << tranche.device;
// will be sorted on first use otherwise
formats.ensureSorted();
for (auto& [format, modifiers]: formats.formats) {
qCDebug(logDmabuf) << " Format" << FourCCStr(format);
if (modifiers.implicit) {
qCDebug(logDmabuf) << " Implicit Modifier";
}
for (const auto& modifier: modifiers.modifiers) {
qCDebug(logDmabuf) << " Explicit Modifier" << FourCCModStr(modifier);
}
}
}
@ -438,7 +737,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
}
WlBuffer* LinuxDmabufManager::createDmabuf(
GbmDeviceHandle& device,
const std::shared_ptr<GbmDevice>& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
@ -457,15 +756,22 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
return nullptr;
}
if (device->renderNode != this->renderNode) {
qCritical(logDmabuf) << "Failed to create gbm_bo: format supports only implicit modifier "
"which does not work across GPUs.";
return nullptr;
}
qCDebug(logDmabuf) << "Creating gbm_bo without modifiers...";
bo = gbm_bo_create(*device, width, height, format, flags);
buffer->usedImplicitModifier = true;
bo = gbm_bo_create(device->device, width, height, format, flags);
} else {
qCDebug(logDmabuf) << "Creating gbm_bo with modifiers...";
STACKLIST_VLA_VIEW(uint64_t, modifiers.modifiers, modifiersData);
bo = gbm_bo_create_with_modifiers2(
*device,
device->device,
width,
height,
format,
@ -514,7 +820,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
params.create_immed(static_cast<int32_t>(width), static_cast<int32_t>(height), format, 0);
params.destroy();
buffer->device = this->dupHandle(device);
buffer->device = device;
buffer->width = width;
buffer->height = height;
buffer->format = format;
@ -561,8 +867,9 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const {
if (request.width != this->width || request.height != this->height) return false;
auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [this](const auto& format) {
if (format.format != this->format) return false;
return format.format == this->format
&& (format.modifiers.isEmpty()
&& (format.modifiers.isEmpty() || (format.implicit && this->usedImplicitModifier)
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
});
@ -814,7 +1121,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) {

View file

@ -2,6 +2,7 @@
#include <array>
#include <cstdint>
#include <memory>
#include <EGL/egl.h>
#include <gbm.h>
@ -72,28 +73,16 @@ private:
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc);
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc);
class GbmDeviceHandle {
public:
GbmDeviceHandle() = default;
GbmDeviceHandle(gbm_device* device): device(device) {}
struct GbmDevice {
GbmDevice(dev_t handle, std::string renderNode, gbm_device* device)
: handle(handle)
, renderNode(std::move(renderNode))
, device(device) {}
~GbmDevice();
Q_DISABLE_COPY_MOVE(GbmDevice);
GbmDeviceHandle(GbmDeviceHandle&& other) noexcept: device(other.device) {
other.device = nullptr;
}
~GbmDeviceHandle();
Q_DISABLE_COPY(GbmDeviceHandle);
GbmDeviceHandle& operator=(GbmDeviceHandle&& other) noexcept {
this->device = other.device;
other.device = nullptr;
return *this;
}
[[nodiscard]] gbm_device* operator*() const { return this->device; }
[[nodiscard]] operator bool() const { return this->device; }
private:
dev_t handle = 0;
std::string renderNode;
gbm_device* device = nullptr;
};
@ -172,7 +161,7 @@ private:
uint32_t stride = 0;
};
GbmDeviceHandle device;
std::shared_ptr<GbmDevice> device;
gbm_bo* bo = nullptr;
wl_buffer* mBuffer = nullptr;
int planeCount = 0;
@ -181,6 +170,7 @@ private:
uint64_t modifier = 0;
uint32_t width = 0;
uint32_t height = 0;
bool usedImplicitModifier = false;
friend class LinuxDmabufManager;
friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer);
@ -239,33 +229,31 @@ public:
[[nodiscard]] WlBuffer* createDmabuf(const WlBufferRequest& request);
[[nodiscard]] WlBuffer* createDmabuf(
GbmDeviceHandle& device,
const std::shared_ptr<GbmDevice>& device,
uint32_t format,
const LinuxDmabufModifiers& modifiers,
uint32_t width,
uint32_t height
);
bool initRenderFormats(QQuickWindow* window);
private:
struct SharedGbmDevice {
dev_t handle = 0;
std::string renderNode;
gbm_device* device = nullptr;
qsizetype refcount = 0;
};
bool initRenderFormatsGl(QQuickWindow* window);
bool initRenderFormatsVk(QQuickWindow* window);
void feedbackDone();
GbmDeviceHandle getGbmDevice(dev_t handle);
void unrefGbmDevice(gbm_device* device);
GbmDeviceHandle dupHandle(const GbmDeviceHandle& handle);
std::shared_ptr<GbmDevice> getGbmDevice(dev_t handle);
LinuxDmabufFormatSelection renderFormats;
std::string renderNode;
QList<LinuxDmabufTranche> tranches;
QList<SharedGbmDevice> gbmDevices;
QList<std::weak_ptr<GbmDevice>> gbmDevices;
WlBufferManagerPrivate* manager;
friend class LinuxDmabufFeedback;
friend class GbmDeviceHandle;
friend struct GbmDevice;
};
} // namespace qs::wayland::buffer::dmabuf

View file

@ -1,5 +1,8 @@
#include "manager.hpp"
#include "manager.hpp"
#include <cstdint>
#include <drm_fourcc.h>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
@ -11,6 +14,7 @@
#include <qvectornd.h>
#include "../../core/logcat.hpp"
#include "../../window/proxywindow.hpp"
#include "dmabuf.hpp"
#include "manager_p.hpp"
#include "qsg.hpp"
@ -22,12 +26,25 @@ namespace {
QS_LOGGING_CATEGORY(logBuffer, "quickshell.wayland.buffer", QtWarningMsg);
}
void WlBufferRequest::DmaFormat::pushMod(uint64_t mod) {
if (mod == DRM_FORMAT_MOD_INVALID) {
this->implicit = true;
} else {
this->modifiers.push(mod);
}
}
bool WlBufferRequest::DmaFormat::modsDefined() const {
return this->implicit || !this->modifiers.isEmpty();
}
void WlBufferRequest::reset() { *this = WlBufferRequest(); }
WlBuffer* WlBufferSwapchain::createBackbuffer(const WlBufferRequest& request, bool* newBuffer) {
static const bool noReuse = qEnvironmentVariableIsSet("QS_NO_BUFFER_REUSE");
auto& buffer = this->presentSecondBuffer ? this->buffer1 : this->buffer2;
if (!buffer || !buffer->isCompatible(request)) {
if (!buffer || !buffer->isCompatible(request) || noReuse) {
buffer.reset(WlBufferManager::instance()->createBuffer(request));
if (newBuffer) *newBuffer = true;
}
@ -44,7 +61,36 @@ WlBufferManager* WlBufferManager::instance() {
return instance;
}
bool WlBufferManager::isReady() const { return this->p->mReady; }
void WlBufferManager::initWindow(QQuickWindow* window) {
if (this->p->mRenderFormatsReady) return;
qCDebug(logBuffer) << "Initializing buffer manager";
static bool initWaiting = false;
if (!window || !window->isSceneGraphInitialized()) {
if (initWaiting) return;
initWaiting = true;
qCDebug(logBuffer) << "Waiting for scene graph to come online";
ProxiedWindow::callOnScenegraphInit([this](QQuickWindow* w) { this->initWindow(w); });
return;
}
qCDebug(logBuffer) << "Initializing render formats";
auto success = this->p->dmabuf.initRenderFormats(window);
if (!success) {
qCWarning(
logBuffer
) << "Render format initialization failed. All buffers will fall back to SHM.";
this->p->mRenderFormatsFailed = true;
}
this->p->mRenderFormatsReady = true;
if (this->p->mDmabufFormatsReady) emit this->ready();
}
bool WlBufferManager::isReady() const {
return this->p->mDmabufFormatsReady && this->p->mRenderFormatsReady;
}
[[nodiscard]] WlBuffer* WlBufferManager::createBuffer(const WlBufferRequest& request) {
static const bool dmabufDisabled = qEnvironmentVariableIsSet("QS_DISABLE_DMABUF");
@ -54,11 +100,16 @@ bool WlBufferManager::isReady() const { return this->p->mReady; }
qCDebug(logBuffer).nospace() << " Dmabuf requests on device " << request.dmabuf.device
<< " (disabled: " << dmabufDisabled << ')';
for (const auto& [format, modifiers]: request.dmabuf.formats) {
qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format)
<< (modifiers.length() == 0 ? " (No modifiers specified)" : "");
for (const auto& format: request.dmabuf.formats) {
qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format.format)
<< (format.modifiers.length() == 0 ? " (No modifiers specified)"
: "");
for (const auto& modifier: modifiers) {
if (format.implicit) {
qCDebug(logBuffer) << " Implicit Modifier";
}
for (const auto& modifier: format.modifiers) {
qCDebug(logBuffer) << " Explicit Modifier" << dmabuf::FourCCModStr(modifier);
}
}
@ -74,7 +125,7 @@ bool WlBufferManager::isReady() const { return this->p->mReady; }
return nullptr;
}
if (!dmabufDisabled) {
if (!dmabufDisabled && !this->p->mRenderFormatsFailed) {
if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf;
qCWarning(logBuffer) << "DMA buffer creation failed, falling back to SHM.";
}
@ -87,8 +138,8 @@ WlBufferManagerPrivate::WlBufferManagerPrivate(WlBufferManager* manager)
, dmabuf(this) {}
void WlBufferManagerPrivate::dmabufReady() {
this->mReady = true;
emit this->manager->ready();
this->mDmabufFormatsReady = true;
if (this->mRenderFormatsReady) emit this->manager->ready();
}
WlBufferQSGDisplayNode::WlBufferQSGDisplayNode(QQuickWindow* window)
@ -123,6 +174,10 @@ void WlBufferQSGDisplayNode::setRect(const QRectF& rect) {
this->setMatrix(matrix);
}
void WlBufferQSGDisplayNode::setFiltering(QSGTexture::Filtering filtering) {
this->imageNode->setFiltering(filtering);
}
void WlBufferQSGDisplayNode::syncSwapchain(const WlBufferSwapchain& swapchain) {
auto* buffer = swapchain.frontbuffer();
auto& texture = swapchain.presentSecondBuffer ? this->buffer2 : this->buffer1;

View file

@ -55,9 +55,12 @@ struct WlBufferRequest {
struct DmaFormat {
DmaFormat() = default;
DmaFormat(uint32_t format): format(format) {}
void pushMod(uint64_t mod);
[[nodiscard]] bool modsDefined() const;
uint32_t format = 0;
StackList<uint64_t, 10> modifiers;
bool implicit = false;
};
struct {
@ -123,6 +126,7 @@ public:
static WlBufferManager* instance();
void initWindow(QQuickWindow* window);
[[nodiscard]] bool isReady() const;
[[nodiscard]] WlBuffer* createBuffer(const WlBufferRequest& request);

View file

@ -14,7 +14,9 @@ public:
WlBufferManager* manager;
dmabuf::LinuxDmabufManager dmabuf;
bool mReady = false;
bool mDmabufFormatsReady = false;
bool mRenderFormatsReady = false;
bool mRenderFormatsFailed = false;
};
} // namespace qs::wayland::buffer

View file

@ -33,6 +33,7 @@ public:
void syncSwapchain(const WlBufferSwapchain& swapchain);
void setRect(const QRectF& rect);
void setFiltering(QSGTexture::Filtering filtering);
private:
QQuickWindow* window;

View file

@ -74,10 +74,23 @@ HyprlandIpc::HyprlandIpc() {
// clang-format on
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; }

View file

@ -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();
@ -155,6 +158,8 @@ private:
HyprlandIpcEvent event {this};
Q_OBJECT_BINDABLE_PROPERTY(HyprlandIpc, bool, bUsingLua, &HyprlandIpc::usingLuaChanged);
Q_OBJECT_BINDABLE_PROPERTY(
HyprlandIpc,
HyprlandMonitor*,

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();
}

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

View file

@ -151,7 +151,20 @@ void HyprlandWorkspace::clearUrgent() {
}
void HyprlandWorkspace::activate() {
if (this->ipc->bindableUsingLua().value()) {
QString name;
for (const auto c: this->bName.value()) {
switch (c.toLatin1()) {
case '"': name.append("\\\""); break;
case '\\': name.append("\\\\"); break;
default: name.append(c); break;
}
}
this->ipc->dispatch(QString("hl.dsp.focus({ workspace = \"%1\" })").arg(name));
} else {
this->ipc->dispatch(QString("workspace %1").arg(this->bName.value()));
}
}
} // namespace qs::hyprland::ipc

View file

@ -11,6 +11,15 @@
namespace qs::wayland {
WlOutputTracker::WlOutputTracker() {
QObject::connect(
static_cast<QGuiApplication*>(QGuiApplication::instance()), // NOLINT
&QGuiApplication::screenRemoved,
this,
&WlOutputTracker::onQScreenRemoved
);
}
void WlOutputTracker::addOutput(::wl_output* output) {
auto* display = QtWaylandClient::QWaylandIntegration::instance()->display();
@ -70,4 +79,11 @@ void WlOutputTracker::onQScreenAdded(QScreen* screen) {
}
}
void WlOutputTracker::onQScreenRemoved(QScreen* screen) {
// FIXME: this cannot catch wl_output removals before full initialization.
// This isn't a correctness problem because the screen is never made available to users
// or dereferenced but it does leave an extra dangling pointer in the list.
if (this->mScreens.removeOne(screen)) emit this->screenRemoved(screen);
}
} // namespace qs::wayland

View file

@ -13,6 +13,8 @@ class WlOutputTracker: public QObject {
Q_OBJECT;
public:
WlOutputTracker();
[[nodiscard]] const QList<QScreen*>& screens() const { return this->mScreens; }
signals:
@ -25,6 +27,7 @@ public slots:
private slots:
void onQScreenAdded(QScreen* screen);
void onQScreenRemoved(QScreen* screen);
private:
QList<QScreen*> mScreens;

View file

@ -21,7 +21,7 @@ void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bo
anchor->updateAnchor();
// If a popup becomes invisble after creation ensure the _q properties will
// If a popup becomes invisible after creation ensure the _q properties will
// be set and not ignored because the rest is the same.
anchor->updatePlacement({popupRole != nullptr, 0}, window->size());

View file

@ -85,7 +85,7 @@ void IccScreencopyContext::ext_image_copy_capture_session_v1_dmabuf_format(
auto reqFormat = buffer::WlBufferRequest::DmaFormat(format);
for (uint16_t i = 0; i != modifierCount; i++) {
reqFormat.modifiers.push(modifierArray[i]); // NOLINT
reqFormat.pushMod(modifierArray[i]); // NOLINT
}
this->request.dmabuf.formats.push(reqFormat);

View file

@ -141,6 +141,8 @@ void ScreencopyView::componentComplete() {
this,
&ScreencopyView::onBuffersReady
);
bufManager->initWindow(this->window());
} else {
this->onBuffersReady();
}
@ -167,6 +169,7 @@ QSGNode* ScreencopyView::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*
auto& swapchain = this->context->swapchain();
node->syncSwapchain(swapchain);
node->setRect(this->boundingRect());
node->setFiltering(QSGTexture::Linear); // NOLINT (misc-include-cleaner)
if (this->mLive) this->context->captureFrame();
return node;

View file

@ -32,11 +32,11 @@ public:
signals:
// This signal is sent once the compositor considers the session to be fully locked.
// This corrosponds to the ext_session_lock_v1::locked event.
// This corresponds to the ext_session_lock_v1::locked event.
void locked();
// This signal is sent once the compositor considers the session to be unlocked.
// This corrosponds to the ext_session_lock_v1::finished event.
// This corresponds to the ext_session_lock_v1::finished event.
//
// The session lock will end in one of three cases.
// 1. unlock() is called.

Some files were not shown because too many files have changed in this diff Show more