Author SHA1 Message Date
outfoxxed 59e9c47b0e version: 0.3.0 2026-05-03 22:09:49 -07:00
outfoxxed ffe95f06ac guix: drop
Hard to keep the package up to date without an active maintainer.
2026-05-03 18:50:15 -07:00
outfoxxed 8db8ca1fec core/log: mask virtual/override property warnings 2026-05-02 00:15:59 -07:00
outfoxxed 0baa81aa03 all: fix typos 2026-04-27 18:56:41 -07:00
outfoxxed 01834e9c8f hyprland/ipc: add lua ipc support 2026-04-27 18:56:39 -07:00
outfoxxed 2ba2ae48b4 core: add qt version check in preprocessor 2026-04-27 02:18:32 -07:00
Jörg Thalheim 055e384668 core/model: make diffUpdate handle reordering without duplicating rows
diffUpdate inserted an element at its target index without removing it
from its previous index, so a pure permutation like [d,a,b,c] ->
[a,b,c,d] left every misplaced element in the model twice and the
removal pass never reclaimed them because their pointer is still in
newValues. Any caller that re-sorts an existing list (dbusmenu
re-layout, desktopentry re-scan) can hit this. Found via a downstream
niri workspace backend where a monitor hot-plug re-sorts the model.
2026-04-26 12:59:02 +02:00
outfoxxed 11a71d233a dbus/dbusmenu: connect onItemPropertiesUpdated
Evidently this was broken at some point or never connected.
2026-04-24 21:01:49 -07:00
outfoxxed e162429b6f wayland/toplevel: track global screen removals
Screen removal events aren't necessarily emitted at a protocol level
when a screen is removed globally.

This would have caused UAFs if the pointer was ever dereferenced, but
in practice it was only compared against.
2026-04-24 01:30:59 -07:00
outfoxxed 783c953987 services/pipewire: assume default channels in channelMap if unset
Pw-pulse seems to have stopped sending these at some point. This
copies the logic that pipewire's tools use to decide the default channels.
2026-04-22 03:34:08 -07:00
outfoxxed b850b8a1a9 core: add DefaultEnv pragma 2026-04-22 01:12:10 -07:00
Carson Powers d60498adc0 networking: add wired device support 2026-04-21 01:48:20 -07:00
outfoxxed 9a54119893 core/command: warn on mismatch when other displays have instances 2026-04-19 02:01:41 -07:00
outfoxxed 18075d1218 core/command: ignore QT_QPA_PLATFORM in display check 2026-04-19 02:01:19 -07:00
outfoxxed fb08eced44 wayland/screencopy: intersect dmabuf and render formats + cleanup
Fixes 99% of MGPU cases.
2026-04-13 00:41:08 -07:00
outfoxxed d4c92973b5 i3/ipc: ensure monitor/workspace pointers are nulled on destroy 2026-04-09 00:34:57 -07:00
outfoxxed 7f7ab6bc8a launch: use dup2 to reset daemon stdio over close+open 2026-04-09 00:10:49 -07:00
99 changed files with 2362 additions and 1342 deletions

No files matched your search

+2 -6
View File
@@ -1,11 +1,7 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
project(quickshell VERSION "0.3.0" LANGUAGES CXX C)
set(UNRELEASED_FEATURES
"network.2"
"colorquant-imagerect"
"window-parent"
)
set(UNRELEASED_FEATURES)
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
+7 -1
View File
@@ -31,10 +31,10 @@ set shell id.
- Added per-corner radius support to Region.
- Added ColorQuantizer region selection.
- Added dialog window support to FloatingWindow.
- Added lua config support to Hyprland module.
## Other Changes
- FreeBSD is now partially supported.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
@@ -44,6 +44,7 @@ set shell id.
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
- Added `DefaultEnv` pragma which sets an environment variable if not already set.
- Unrecognized pragmas are no longer a hard error for future backward compatibility.
## Bug Fixes
@@ -73,6 +74,11 @@ set shell id.
- 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
+1 -1
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
-77
View File
@@ -1,77 +0,0 @@
(define-module (quickshell)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (gnu packages cpp)
#:use-module (gnu packages freedesktop)
#:use-module (gnu packages gcc)
#:use-module (gnu packages gl)
#:use-module (gnu packages jemalloc)
#:use-module (gnu packages linux)
#:use-module (gnu packages ninja)
#:use-module (gnu packages pkg-config)
#:use-module (gnu packages qt)
#:use-module (gnu packages vulkan)
#:use-module (gnu packages xdisorg)
#:use-module (gnu packages xorg)
#:use-module (guix build-system cmake)
#:use-module (guix download)
#:use-module (guix gexp)
#:use-module (guix git-download)
#:use-module (guix packages)
#:use-module (guix packages)
#:use-module (guix utils))
(define-public quickshell-git
(package
(name "quickshell")
(version "git")
(source (local-file "." "quickshell-checkout"
#:recursive? #t
#:select? (or (git-predicate (current-source-directory))
(const #t))))
(build-system cmake-build-system)
(propagated-inputs (list qtbase qtdeclarative qtsvg))
(native-inputs (list ninja
gcc-14
pkg-config
qtshadertools
spirv-tools
wayland-protocols
cli11))
(inputs (list jemalloc
libdrm
libxcb
libxkbcommon
linux-pam
polkit
mesa
pipewire
qtbase
qtdeclarative
qtwayland
vulkan-headers
wayland))
(arguments
(list #:tests? #f
#:configure-flags
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
"-DCRASH_HANDLER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
(replace 'install (lambda _ (invoke "cmake" "--install" ".")))
(add-after 'install 'wrap-program
(lambda* (#:key inputs #:allow-other-keys)
(wrap-program (string-append #$output "/bin/quickshell")
`("QML_IMPORT_PATH" ":"
= (,(getenv "QML_IMPORT_PATH")))))))))
(home-page "https://quickshell.outfoxxed.me")
(synopsis "QtQuick-based desktop shell toolkit")
(description
"Quickshell is a flexible QtQuick-based toolkit for creating and
customizing toolbars, notification centers, and other desktop
environment tools in a live programming environment.")
(license license:lgpl3)))
quickshell-git
-1
View File
@@ -43,7 +43,6 @@ qt_add_library(quickshell-core STATIC
toolsupport.cpp
streamreader.cpp
debuginfo.cpp
hook.cpp
)
qt_add_qml_module(quickshell-core
+1 -1
View File
@@ -89,7 +89,7 @@ class ColorQuantizer
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
/// > reccommended to rescale, otherwise the quantization process will take much longer.
/// > recommended to rescale, otherwise the quantization process will take much longer.
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
public:
-86
View File
@@ -1,86 +0,0 @@
#include <dlfcn.h>
#include <array>
#include <cstdlib>
#include <string>
#include <cpptrace/basic.hpp>
#include <QCoreApplication>
#include <QEvent>
#include <QLoggingCategory>
#include <QMetaObject>
#include <QObject>
__attribute__((constructor)) static void initHook() {
unsetenv("LD_PRELOAD");
}
Q_LOGGING_CATEGORY(logDestructors, "quickshell.destructors");
Q_LOGGING_CATEGORY(logPostEvent, "quickshell.postEvent");
static std::string traceAddrs() {
std::array<cpptrace::frame_ptr, 64> frames{};
auto count = cpptrace::safe_generate_raw_trace(frames.data(), frames.size(), 1);
std::string result;
for (size_t i = 0; i < count; i++) {
if (i > 0) result += ',';
char buf[20];
snprintf(buf, sizeof(buf), "%p", reinterpret_cast<void*>(frames[i]));
result += buf;
}
return result;
}
using VoidMemberFn = void (*)(QObject*);
static VoidMemberFn realD2() {
static auto fn = reinterpret_cast<VoidMemberFn>(dlsym(RTLD_NEXT, "_ZN7QObjectD2Ev"));
return fn;
}
static VoidMemberFn realDeleteLater() {
static auto fn =
reinterpret_cast<VoidMemberFn>(dlsym(RTLD_NEXT, "_ZN7QObject11deleteLaterEv"));
return fn;
}
using PostEventFn = void (*)(QObject*, QEvent*, int);
static PostEventFn realPostEvent() {
static auto fn = reinterpret_cast<PostEventFn>(
dlsym(RTLD_NEXT, "_ZN16QCoreApplication9postEventEP7QObjectP6QEventi")
);
return fn;
}
extern "C" {
void _ZN7QObjectD2Ev(QObject* self) {
const auto* meta = self->metaObject();
auto trace = traceAddrs();
qCDebug(logDestructors, "~QObject %p %s\n%s", static_cast<void*>(self), meta->className(), trace.c_str());
realD2()(self);
}
void _ZN7QObject11deleteLaterEv(QObject* self) {
const auto* meta = self->metaObject();
auto trace = traceAddrs();
qCDebug(logDestructors, "deleteLater %p %s\n%s", static_cast<void*>(self), meta->className(), trace.c_str());
realDeleteLater()(self);
}
void _ZN16QCoreApplication9postEventEP7QObjectP6QEventi(
QObject* receiver,
QEvent* event,
int priority
) {
//if (event->type() == QEvent::User + 1) {
const auto* meta = receiver->metaObject();
auto trace = traceAddrs();
qCDebug(logPostEvent, "%p %d %s\n%s", static_cast<void*>(receiver), event->type(), meta->className(), trace.c_str());
//}
realPostEvent()(receiver, event, priority);
}
} // extern "C"
+1 -1
View File
@@ -14,7 +14,7 @@
#include "generation.hpp"
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
class PixmapCacheIconEngine: public QIconEngine {
void paint(
QPainter* /*unused*/,
+16 -4
View File
@@ -257,9 +257,21 @@ void LogManager::filterCategory(QLoggingCategory* category) {
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
// clang-format on
} else if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
} else {
if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
}
// Hides virtual/override property warnings.
// Getting rid of this is blocked by https://qt-project.atlassian.net/browse/QTBUG-145977
// for internal Quickshell types, and may still be desired for shells that want to maintain
// compatibility with Qt versions prior to 6.11.
if (categoryName == QLatin1StringView("qt.qml.propertyCache.append")
&& !qEnvironmentVariableIsSet("QS_NO_FILTER_QT_LOGS"))
{
filter.warn = false;
}
}
for (const auto& rule: *instance->rules) {
@@ -530,7 +542,7 @@ void ThreadLogging::initFs() {
Qt::QueuedConnection
);
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
}
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
+4
View File
@@ -138,6 +138,10 @@ public:
qsizetype oi = 0;
for (auto* object: newValues) {
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
// object may already be present further down (reorder case);
// drop the old row first so the same pointer is not inserted twice.
auto old = this->mValuesList.indexOf(object, oi);
if (old != -1) this->removeAt(old);
this->insertObject(object, oi);
}
+12 -9
View File
@@ -1,6 +1,7 @@
#include "paths.hpp"
#include <cerrno>
#include <cstdio>
#include <tuple>
#include <utility>
#include <fcntl.h>
@@ -9,7 +10,6 @@
#include <qdir.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qpair.h>
#include <qstandardpaths.h>
#include <qtenvironmentvariables.h>
#include <qtversionchecks.h>
@@ -412,10 +412,11 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
return true;
}
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths) << "Collecting instances from" << path;
auto liveInstances = QVector<InstanceLockInfo>();
auto mismatchedInstances = QVector<InstanceLockInfo>();
auto deadInstances = QVector<InstanceLockInfo>();
auto dir = QDir(path);
@@ -427,20 +428,22 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path;
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
if (info.pid == -1) {
deadInstances.push_back(info);
continue;
}
if (info.pid == -1) {
deadInstances.push_back(info);
} else {
liveInstances.push_back(info);
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
mismatchedInstances.push_back(info);
continue;
}
liveInstances.push_back(info);
} else {
qCDebug(logPaths) << "Skipped potential instance at" << path;
}
}
return qMakePair(liveInstances, deadInstances);
return {liveInstances, mismatchedInstances, deadInstances};
}
+4 -1
View File
@@ -1,4 +1,7 @@
#pragma once
#include <tuple>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdir.h>
#include <qpair.h>
@@ -29,7 +32,7 @@ public:
static QString ipcPath(const QString& id);
static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
collectInstances(const QString& path, const QString& display);
QDir* baseRunDir();
+8
View File
@@ -18,8 +18,10 @@
#include <qscreen.h>
#include <qtenvironmentvariables.h>
#include <qtmetamacros.h>
#include <qtversion.h>
#include <qtypes.h>
#include <qvariant.h>
#include <qversionnumber.h>
#include <qwindowdefs.h>
#include <unistd.h>
@@ -343,6 +345,12 @@ bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
return QuickshellGlobal::hasVersion(major, minor, QStringList());
}
bool QuickshellGlobal::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
auto* qsg = new QuickshellGlobal();
auto* generation = EngineGeneration::findEngineGeneration(engine);
+12 -1
View File
@@ -244,7 +244,7 @@ public:
/// it is assumed that all unreleased features are present. The unreleased feature list
/// may be omitted.
///
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
@@ -254,6 +254,17 @@ public:
/// > ```
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
/// Check if Qt's version is at least `major.minor`.
///
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
/// > //@ if hasVersion(6, 10)
/// > ...
/// > //@ endif
/// > ```
Q_INVOKABLE static bool hasQtVersion(qint32 major, qint32 minor);
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
+1 -1
View File
@@ -15,7 +15,7 @@
/// Monitor object useful for setting the monitor for a @@QsWindow
/// or querying information about the monitor.
///
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
/// > [!WARNING] If the monitor is disconnected, then any stored copies of its ShellMonitor will
/// > be marked as dangling and all properties will return default values.
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
///
+1 -1
View File
@@ -242,7 +242,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
}
QRegion PendingRegion::applyTo(const QRect& rect) const {
// if left as the default, dont combine it with the whole rect area, leave it as is.
// if left as the default, don't combine it with the whole rect area, leave it as is.
if (this->mIntersection == Intersection::Combine) {
return this->build();
} else {
+1 -1
View File
@@ -116,7 +116,7 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
auto* reloadable = qobject_cast<Reloadable*>(child);
if (reloadable != nullptr) {
if (reloadable->mReloadableId == reloadId) return reloadable;
// if not then don't check its children as thats a seperate reload scope.
// if not then don't check its children as that's a separate reload scope.
} else {
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
if (reloadable != nullptr) return reloadable;
+1 -1
View File
@@ -21,7 +21,7 @@ class Reloadable
/// previous state.
///
/// Simply keeping a stable identifier across config versions (saves) is
/// enough to help the reloader figure out which object in the old revision corrosponds to
/// enough to help the reloader figure out which object in the old revision corresponds to
/// this object in the current revision, and facilitate smoother reloading.
///
/// Note that identifiers are scoped, and will try to do the right thing in context.
+3 -3
View File
@@ -89,7 +89,7 @@ void QmlScanner::scanDir(const QDir& dir) {
QString qmldir;
auto stream = QTextStream(&qmldir);
// cant derive a module name if not in shell path
// can't derive a module name if not in shell path
if (path.startsWith(this->rootPath.path())) {
auto end = path.sliced(this->rootPath.path().length());
@@ -160,7 +160,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
if (!singleton && line == "pragma Singleton") {
singleton = true;
} else if (line.startsWith("import")) {
// we dont care about "import qs" as we always load the root folder
// we don't care about "import qs" as we always load the root folder
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
importCursor += 4;
QString path;
@@ -211,7 +211,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
mask = false;
}
if (!sourceMasked && mask) hideMask = true;
mask = sourceMasked || mask; // cant unmask if a nested if passes
mask = sourceMasked || mask; // can't unmask if a nested if passes
ifScopes.append(mask);
if (mask) isOverridden = true;
sourceMasked = mask;
+8
View File
@@ -2,6 +2,8 @@
#include <qcontainerfwd.h>
#include <qtenvironmentvariables.h>
#include <qtversion.h>
#include <qversionnumber.h>
#include "build.hpp"
@@ -20,6 +22,12 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
}
bool PreprocEnv::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QString PreprocEnv::env(const QString& variable) {
return qEnvironmentVariable(variable.toStdString().c_str());
}
+1
View File
@@ -12,6 +12,7 @@ class PreprocEnv: public QObject {
public:
Q_INVOKABLE static bool
hasVersion(int major, int minor, const QStringList& features = QStringList());
Q_INVOKABLE static bool hasQtVersion(int major, int minor);
Q_INVOKABLE static QString env(const QString& variable);
Q_INVOKABLE static bool isEnvSet(const QString& variable);
+1
View File
@@ -8,3 +8,4 @@ qs_test(transformwatcher transformwatcher.cpp)
qs_test(ringbuffer ringbuf.cpp)
qs_test(scriptmodel scriptmodel.cpp)
qs_test(stacklist stacklist.cpp)
qs_test(objectmodel objectmodel.cpp)
+47
View File
@@ -0,0 +1,47 @@
#include "objectmodel.hpp"
#include <qlist.h>
#include <qobject.h>
#include <qtest.h>
#include <qtestcase.h>
#include "../model.hpp"
void TestObjectModel::diffUpdateInsertRemove() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c}));
// drop b, append d — relative order of survivors unchanged
model.diffUpdate({&a, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &c, &d}));
}
void TestObjectModel::diffUpdateReorder() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&d);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&d, &a, &b, &c}));
// pure permutation: same elements, different order. Previously turned
// [d,a,b,c] into [a,b,c,d,a,b,c] because the old row was never removed
// before inserting at the new index.
model.diffUpdate({&a, &b, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c, &d}));
}
QTEST_MAIN(TestObjectModel);
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <qobject.h>
#include <qtmetamacros.h>
class TestObjectModel: public QObject {
Q_OBJECT;
private slots:
static void diffUpdateInsertRemove();
static void diffUpdateReorder();
};
+1 -1
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"
+1 -1
View File
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
{
if (this->mInstances.contains(variant)) {
continue; // we dont need to recreate this one
continue; // we don't need to recreate this one
}
auto variantMap = QVariantMap();
+1 -1
View File
@@ -43,7 +43,7 @@ class Variants: public Reloadable {
Q_OBJECT;
/// The component to create instances of.
///
/// The delegate should define a `modelData` property that will be popuplated with a value
/// The delegate should define a `modelData` property that will be populated with a value
/// from the @@model.
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
/// The list of sets of properties to create instances with.
+7
View File
@@ -301,6 +301,13 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
&DBusMenu::onLayoutUpdated
);
QObject::connect(
this->interface,
&DBusMenuInterface::ItemsPropertiesUpdated,
this,
&DBusMenu::onItemPropertiesUpdated
);
this->properties.setInterface(this->interface);
this->properties.updateAllViaGetAll();
}
+1 -1
View File
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
return;
}
// make sure we dont miss any delimiters in the buffer if the delimiter changes
// make sure we don't miss any delimiters in the buffer if the delimiter changes
if (this->mSplitMarkerChanged) {
this->mSplitMarkerChanged = false;
this->parseBytes(buffer, buffer);
+2 -2
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,
+2 -2
View File
@@ -96,7 +96,7 @@ class SocketServer: public Reloadable {
Q_OBJECT;
/// If the socket server is currently active. Defaults to false.
///
/// Setting this to false will destory all active connections and delete
/// Setting this to false will destroy all active connections and delete
/// the socket file on disk.
///
/// If path is empty setting this property will have no effect.
@@ -105,7 +105,7 @@ class SocketServer: public Reloadable {
///
/// Setting this property while the server is active will have no effect.
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
/// Connection handler component. Must creeate a @@Socket.
/// Connection handler component. Must create a @@Socket.
///
/// The created socket should not set @@connected or @@path or the incoming
/// socket connection will be dropped (they will be set by the socket server.)
+1 -1
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."
+38 -12
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 {
@@ -213,7 +228,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
for (auto& instance: instances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
<< (instance.pid == -1 ? " (dead)" : "");
<< (instance.pid == -1 ? "(dead)" : "");
}
return -1;
@@ -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";
}
}
+23 -13
View File
@@ -76,6 +76,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
bool useSystemStyle = false;
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
QHash<QString, QString> envOverrides;
QHash<QString, QString> defaultEnv;
QString appId = qEnvironmentVariable("QS_APP_ID");
bool dropExpensiveFonts = false;
QString dataDir;
@@ -89,25 +90,30 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
if (line.startsWith("//@ pragma ")) {
auto pragma = line.sliced(11).trimmed();
if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("Env ")) {
auto envPragma = pragma.sliced(4);
auto splitIdx = envPragma.indexOf('=');
auto isEnv = pragma.startsWith("Env ");
auto isDefaultEnv = pragma.startsWith("DefaultEnv ");
if (isEnv || isDefaultEnv) {
auto content = pragma.sliced(isDefaultEnv ? 11 : 4);
auto splitIdx = content.indexOf('=');
if (splitIdx == -1) {
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
return -1;
}
auto var = envPragma.sliced(0, splitIdx).trimmed();
auto val = envPragma.sliced(splitIdx + 1).trimmed();
pragmas.envOverrides.insert(var, val);
} else if (pragma.startsWith("AppId ")) {
auto var = content.sliced(0, splitIdx).trimmed();
auto val = content.sliced(splitIdx + 1).trimmed();
if (isDefaultEnv) pragmas.defaultEnv.insert(var, val);
else pragmas.envOverrides.insert(var, val);
} else if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("AppId ")) {
pragmas.appId = pragma.sliced(6).trimmed();
} else if (pragma.startsWith("ShellId ")) {
shellId = pragma.sliced(8).trimmed();
@@ -174,6 +180,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
}
for (auto [var, val]: pragmas.defaultEnv.asKeyValueRange()) {
if (!qEnvironmentVariableIsSet(var.toUtf8())) qputenv(var.toUtf8(), val.toUtf8());
}
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
qputenv(var.toUtf8(), val.toUtf8());
}
+18 -10
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) {
+2
View File
@@ -3,8 +3,10 @@ add_subdirectory(nm)
qt_add_library(quickshell-network STATIC
network.cpp
device.cpp
wired.cpp
wifi.cpp
enums.cpp
qml.cpp
)
target_include_directories(quickshell-network PRIVATE
+4
View File
@@ -9,6 +9,7 @@
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
@@ -45,4 +46,7 @@ void NetworkDevice::disconnect() {
this->requestDisconnect();
}
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
+17 -2
View File
@@ -7,12 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! A network device.
/// The @@type property may be used to determine if this device is a @@WifiDevice.
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
class NetworkDevice: public QObject {
Q_OBJECT;
QML_ELEMENT;
@@ -20,11 +22,17 @@ class NetworkDevice: public QObject {
// clang-format off
/// The device type.
///
/// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
/// When the device type is `Wired`, the device object is a @@WiredDevice.
/// connection and scanning.
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// A list of available or connected networks for this device.
///
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
/// True if the device is connected.
@@ -45,6 +53,10 @@ public:
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
virtual void networkAdded(Network* net);
virtual void networkRemoved(Network* net);
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QString name() const { return this->bName; }
@@ -69,6 +81,9 @@ signals:
void nmManagedChanged();
void autoconnectChanged();
protected:
ObjectModel<Network> mNetworks {this};
private:
DeviceType::Enum mType;
// clang-format off
+2 -1
View File
@@ -38,7 +38,7 @@ QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) {
switch (reason) {
case Unknown: return QStringLiteral("Unknown");
case NoSecrets: return QStringLiteral("Secrets were required but not provided");
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant diconnected");
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant disconnected");
case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed");
case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate");
case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found");
@@ -50,6 +50,7 @@ QString DeviceType::toString(DeviceType::Enum type) {
switch (type) {
case None: return QStringLiteral("None");
case Wifi: return QStringLiteral("Wifi");
case Wired: return QStringLiteral("Wired");
default: return QStringLiteral("Unknown");
}
}
+1
View File
@@ -98,6 +98,7 @@ public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
+2
View File
@@ -1,9 +1,11 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"qml.hpp",
"network.hpp",
"device.hpp",
"wifi.hpp",
"wired.hpp",
"enums.hpp",
"nm/settings.hpp",
]
+4 -65
View File
@@ -6,12 +6,9 @@
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
#include "nm/settings.hpp"
namespace qs::network {
@@ -20,68 +17,10 @@ namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
Network::Network(QString name, NetworkDevice* device, QObject* parent)
: QObject(parent)
, bName(std::move(name))
, mDevice(device) {
this->bStateChanging.setBinding([this] {
auto state = this->bState.value();
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
+17 -143
View File
@@ -7,147 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
namespace qs::network {
class NetworkDevice;
}
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
namespace qs::network {
///! A network.
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
@@ -158,11 +25,13 @@ class Network: public QObject {
// clang-format off
/// The name of the network.
Q_PROPERTY(QString name READ name CONSTANT);
/// A list of NetworkManager connnection settings profiles for this network.
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// The device this network belongs to.
Q_PROPERTY(NetworkDevice* device READ device CONSTANT);
/// A list of NetworkManager connection settings profiles for this network.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_PROPERTY(QList<NMSettings*> nmSettings READ nmSettings NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
/// True if the network is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// True if the wifi network has known connection settings saved.
@@ -174,7 +43,7 @@ class Network: public QObject {
// clang-format on
public:
explicit Network(QString name, QObject* parent = nullptr);
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network.
///
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
@@ -194,7 +63,9 @@ public:
void settingsRemoved(NMSettings* settings);
// clang-format off
[[nodiscard]] QString name() const { return this->mName; }
[[nodiscard]] QString name() const { return this->bName; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
QBindable<bool> bindableConnected() { return &this->bConnected; }
@@ -208,6 +79,7 @@ signals:
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
void connectionFailed(ConnectionFailReason::Enum reason);
void nameChanged();
void connectedChanged();
void knownChanged();
void stateChanged();
@@ -219,15 +91,17 @@ signals:
QSDOC_HIDE void requestForget();
protected:
QString mName;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, QList<NMSettings*>, bNmSettings, &Network::nmSettingsChanged);
// clang-format on
private:
NetworkDevice* mDevice;
};
} // namespace qs::network
+13
View File
@@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
dbus_nm_wireless
)
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES
CLASSNAME DBusNMWiredProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Device.Wired.xml
dbus_nm_wired
)
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
CLASSNAME DBusNMAccessPointProxy
NO_NAMESPACE TRUE
@@ -67,8 +77,10 @@ qt_add_library(quickshell-network-nm STATIC
settings.cpp
accesspoint.cpp
wireless.cpp
wired.cpp
utils.cpp
dbus_types.cpp
network.cpp
enums.hpp
${NM_DBUS_INTERFACES}
)
@@ -79,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
qs_add_link_dependencies(quickshell-network-nm quickshell-network)
+1 -1
View File
@@ -7,7 +7,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "../enums.hpp"
#include "dbus_nm_accesspoint.h"
#include "enums.hpp"
+7 -68
View File
@@ -15,15 +15,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "wired.hpp"
#include "wireless.hpp"
namespace qs::network {
@@ -133,6 +131,7 @@ void NetworkManager::registerDevice(const QString& path) {
switch (type) {
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break;
default: break;
}
@@ -147,8 +146,9 @@ void NetworkManager::registerDevice(const QString& path) {
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
// clang-format on
this->registerFrontendDevice(type, dev);
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
emit this->deviceAdded(dev->frontend());
});
}
} else {
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
@@ -160,67 +160,6 @@ void NetworkManager::registerDevice(const QString& path) {
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
}
void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) {
NetworkDevice* frontendDev = nullptr;
switch (type) {
case NMDeviceType::Wifi: {
auto* frontendWifiDev = new WifiDevice(dev);
auto* wifiDev = qobject_cast<NMWirelessDevice*>(dev);
// Bind WifiDevice-specific properties
auto translateMode = [wifiDev]() {
switch (wifiDev->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
// clang-format off
frontendWifiDev->bindableMode().setBinding(translateMode);
wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); });
QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded);
QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved);
// clang-format on
frontendDev = frontendWifiDev;
break;
}
default: return;
}
// Bind generic NetworkDevice properties
auto translateState = [dev]() {
switch (dev->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
frontendDev->bindableState().setBinding(translateState);
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
frontendDev->bindableNmManaged().setBinding([dev]() { return dev->managed(); });
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetNmManaged, dev, &NMDevice::setManaged);
// clang-format on
this->mFrontendDevices.insert(dev->path(), frontendDev);
emit this->deviceAdded(frontendDev);
}
void NetworkManager::removeFrontendDevice(NMDevice* dev) {
auto* frontendDev = this->mFrontendDevices.take(dev->path());
if (frontendDev) {
emit this->deviceRemoved(frontendDev);
frontendDev->deleteLater();
}
}
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
this->registerDevice(path.path());
}
@@ -235,7 +174,7 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
this->mDevices.erase(iter);
if (dev) {
qCDebug(logNetworkManager) << "Device removed:" << path.path();
this->removeFrontendDevice(dev);
emit this->deviceRemoved(dev->frontend());
delete dev;
}
}
+1 -4
View File
@@ -8,7 +8,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../network.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_types.hpp"
#include "device.hpp"
@@ -70,11 +70,8 @@ private:
void init();
void registerDevices();
void registerDevice(const QString& path);
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
void removeFrontendDevice(NMDevice* dev);
QHash<QString, NMDevice*> mDevices;
QHash<QString, NetworkDevice*> mFrontendDevices;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
+48
View File
@@ -14,9 +14,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
@@ -49,6 +53,29 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
this->deviceProperties.updateAllViaGetAll();
}
void NMDevice::bindFrontend(NetworkDevice* frontend) {
auto translateState = [this]() {
switch (this->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontend->bindableName().setBinding([this]() { return this->interface(); });
frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); });
frontend->bindableState().setBinding(translateState);
frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); });
frontend->bindableNmManaged().setBinding([this]() { return this->managed(); });
QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect);
QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged);
QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded);
QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved);
}
void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) {
auto enumReason = static_cast<NMDeviceStateReason::Enum>(reason);
auto enumNewState = static_cast<NMDeviceState::Enum>(newState);
@@ -57,6 +84,22 @@ void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 re
this->bStateReason = enumReason;
}
void NMDevice::bindNetwork(NMNetwork* net) {
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){
emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path()));
});
QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){
emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject));
});
QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) {
if (visible) emit this->networkAdded(net->frontend());
else emit this->networkRemoved(net->frontend());
});
if (net->visible()) emit this->networkAdded(net->frontend());
}
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
const QString stringPath = path.path();
@@ -160,4 +203,9 @@ DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
}
DBusResult<qs::network::NMDeviceInterfaceFlags::Enum>
DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceInterfaceFlags::Enum>(wire));
}
} // namespace qs::dbus
+25 -2
View File
@@ -8,9 +8,11 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../enums.hpp"
#include "../device.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@@ -22,6 +24,13 @@ struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
static DBusResult<Data> fromWire(Wire wire);
};
template <>
struct DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum> {
using Wire = quint32;
using Data = qs::network::NMDeviceInterfaceFlags::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
@@ -44,16 +53,23 @@ public:
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }
[[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; }
[[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; }
[[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const {
return this->bInterfaceFlags;
}
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }
[[nodiscard]] virtual NetworkDevice* frontend() = 0;
signals:
void loaded();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& apPath
const QDBusObjectPath& specificObjectPath
);
void networkAdded(Network* net);
void networkRemoved(Network* net);
void settingsLoaded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
@@ -66,12 +82,17 @@ signals:
void stateReasonChanged(NMDeviceStateReason::Enum reason);
void lastFailReasonChanged(NMDeviceStateReason::Enum reason);
void autoconnectChanged(bool autoconnect);
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
public slots:
void disconnect();
void setAutoconnect(bool autoconnect);
void setManaged(bool managed);
protected:
void bindFrontend(NetworkDevice* frontend);
void bindNetwork(NMNetwork* net);
private slots:
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
@@ -93,6 +114,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableSettingsPathsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
@@ -102,6 +124,7 @@ private:
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags");
// clang-format on
DBusNMDeviceProxy* deviceProxy = nullptr;
+17
View File
@@ -183,6 +183,23 @@ public:
Q_ENUM(Enum);
};
// Flags for a network interface.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags.
class NMDeviceInterfaceFlags: public QObject {
Q_OBJECT;
public:
enum Enum : quint32 {
None = 0x0,
Up = 0x1,
LowerUp = 0x2,
Promisc = 0x4,
Carrier = 0x10000,
LldpClientEnabled = 0x20000,
};
Q_ENUM(Enum);
};
// 802.11 specific device encryption and authentication capabilities.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
class NMWirelessCapabilities: public QObject {
+308
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
View File
@@ -0,0 +1,138 @@
#pragma once
#include <qobject.h>
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "active_connection.hpp"
#include "enums.hpp"
#include "settings.hpp"
namespace qs::network {
// NMNetwork aggregates NMActiveConnections and NMSettings of the same network.
class NMNetwork: public QObject {
Q_OBJECT;
public:
explicit NMNetwork(QObject* parent = nullptr);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
void connect(const QString& devPath);
void connectWithSettings(const QString& devPath, NMSettings* settings);
// clang-format off
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; }
[[nodiscard]] virtual Network* frontend() = 0;
QBindable<bool> bindableVisible() { return &this->bVisible; }
[[nodiscard]] bool visible() const { return this->bVisible; }
// clang-format on
signals:
void requestDisconnect();
void requestActivateConnection(const QString& settingsPath);
void
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void referenceSettingsChanged(NMSettings* settings);
void visibilityChanged(bool visible);
protected:
void bindFrontend(Network* frontend);
QHash<QString, NMSettings*> mSettings;
Q_OBJECT_BINDABLE_PROPERTY(
NMNetwork,
NMSettings*,
bReferenceSettings,
&NMNetwork::referenceSettingsChanged
);
private:
void updateReferenceSettings();
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged);
// clang-format on
};
// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network.
// This is useful for devices with one network that don't need to extend the base Network class.
class NMGenericNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr);
[[nodiscard]] Network* frontend() override { return this->mFrontend; }
private:
void bindFrontend();
Network* mFrontend;
};
// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality.
class NMWirelessNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
[[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; };
// clang-format on
signals:
void disappeared();
void signalStrengthChanged(quint8 signal);
void securityChanged(WifiSecurityType::Enum security);
void activeApPathChanged(QString path);
void referenceApChanged(NMAccessPoint* ap);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void apRemoved(NMAccessPoint* ap);
private:
void updateReferenceAp();
void bindFrontend();
WifiNetwork* mFrontend;
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged);
// clang-format on
};
} // namespace qs::network
@@ -0,0 +1,4 @@
<node>
<interface name="org.freedesktop.NetworkManager.Device.Wired">
</interface>
</node>
+1
View File
@@ -37,6 +37,7 @@ NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
qDBusRegisterMetaType<QList<NMIPv6Address>>();
qDBusRegisterMetaType<NMIPv6Route>();
qDBusRegisterMetaType<QList<NMIPv6Route>>();
qDBusRegisterMetaType<QMap<QString, QString>>();
this->proxy = new DBusNMConnectionSettingsProxy(
"org.freedesktop.NetworkManager",
+26 -2
View File
@@ -273,6 +273,7 @@ void manualSettingDemarshall(NMSettingsMap& map) {
if (signature == "aa{sv}") return QVariant::fromValue(qdbus_cast<QList<QVariantMap>>(arg));
if (signature == "a(ayuay)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Address>>(arg));
if (signature == "a(ayuayu)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Route>>(arg));
if (signature == "a{ss}") return QVariant::fromValue(qdbus_cast<QMap<QString, QString>>(arg));
return value;
};
@@ -291,7 +292,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
if (s == "802-1x.ca-cert" || s == "802-1x.client-cert" || s == "802-1x.private-key"
|| s == "802-1x.password-raw" || s == "802-1x.phase2-ca-cert"
|| s == "802-1x.phase2-client-cert" || s == "802-1x.phase2-private-key"
|| s == "802-11-wireless.ssid")
|| s == "802-11-wireless.ssid" || s == "802-3-ethernet.cloned-mac-address"
|| s == "802-3-ethernet.mac-address")
{
if (value.typeId() == QMetaType::QString) {
return value.toString().toUtf8();
@@ -423,7 +425,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
// QVariantList -> QStringList
if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search"
|| s == "802-11-wireless.seen-bssids")
|| s == "802-11-wireless.seen-bssids" || s == "802-3-ethernet.mac-address-blacklist"
|| s == "802-3-ethernet.mac-address-denylist" || s == "802-3-ethernet.s390-subchannels")
{
if (value.typeId() == QMetaType::QVariantList) {
QStringList stringList;
@@ -435,6 +438,18 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
return QVariant();
}
// QVariantMap -> QMap<QString, QString>
if (s == "802-3-ethernet.s390-options") {
if (value.canConvert<QVariantMap>()) {
QMap<QString, QString> r;
for (const auto& [key, val]: value.toMap().asKeyValueRange()) {
r.insert(key, val.toString());
}
return QVariant::fromValue(r);
}
return QVariant();
}
// double (whole number) -> qint32
if (value.typeId() == QMetaType::Double) {
auto num = value.toDouble();
@@ -498,6 +513,15 @@ QVariant settingTypeToQml(const QVariant& value) {
return out;
}
// QMap<QString, QString> -> QVariantMap
if (value.userType() == qMetaTypeId<QMap<QString, QString>>()) {
QVariantMap out;
for (const auto& [key, val]: value.value<QMap<QString, QString>>().asKeyValueRange()) {
out.insert(key, val);
}
return out;
}
return value;
}
+101
View File
@@ -0,0 +1,101 @@
#include "wired.hpp"
#include <qdbusconnection.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWiredDevice::NMWiredDevice(const QString& path, QObject* parent): NMDevice(path, parent) {
this->wiredProxy = new DBusNMWiredProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->wiredProxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for wired device at" << path;
return;
}
// Wait to create the NMGenericNetwork/Network
// until the dbus properties load because the network requires the interface name.
QObject::connect(
&this->wiredProperties,
&DBusPropertyGroup::getAllFinished,
this,
&NMWiredDevice::initWired,
Qt::SingleShotConnection
);
this->wiredProperties.setInterface(this->wiredProxy);
this->wiredProperties.updateAllViaGetAll();
// Register and bind the frontend WiredDevice.
this->mFrontend = new WiredDevice(this);
this->bindFrontend();
};
void NMWiredDevice::initWired() {
// Register the NMGenericNetwork and bind the frontend Network.
// For wired networking, there is only one Network and it should exist for the lifetime of the device.
auto* net = new NMGenericNetwork(QString(), this->frontend(), this);
net->frontend()->bindableName().setBinding([this]() { return this->interface(); });
auto visible = [this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
};
net->bindableVisible().setBinding(visible);
this->NMDevice::bindNetwork(net);
this->mNetwork = net;
// clang-format off
QObject::connect(this, &NMWiredDevice::settingsLoaded, this, &NMWiredDevice::onSettingsLoaded);
QObject::connect(this, &NMWiredDevice::activeConnectionLoaded, this, &NMWiredDevice::onActiveConnectionLoaded);
// clang-format on
emit this->loaded();
}
void NMWiredDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
frontend->bindableLinkSpeed().setBinding([this]() { return this->bSpeed.value(); });
frontend->bindableHasLink().setBinding([this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
});
}
void NMWiredDevice::onSettingsLoaded(NMSettings* settings) {
this->mNetwork->addSettings(settings);
}
void NMWiredDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
this->mNetwork->addActiveConnection(active);
}
bool NMWiredDevice::isValid() const {
return this->NMDevice::isValid() && (this->wiredProxy && this->wiredProxy->isValid());
}
} // namespace qs::network
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <qdbusextratypes.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wired interface
// Owns the lifetime of a NMGenericNetwork, and a frontend WiredDevice.
class NMWiredDevice: public NMDevice {
Q_OBJECT;
public:
explicit NMWiredDevice(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const override;
[[nodiscard]] WiredDevice* frontend() override { return this->mFrontend; };
signals:
void speedChanged(quint32 speed);
private slots:
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
private:
void initWired();
void bindFrontend();
WiredDevice* mFrontend = nullptr;
NMGenericNetwork* mNetwork = nullptr;
Q_OBJECT_BINDABLE_PROPERTY(NMWiredDevice, quint32, bSpeed, &NMWiredDevice::speedChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wiredProperties);
QS_DBUS_PROPERTY_BINDING(NMWiredDevice, pSpeed, bSpeed, wiredProperties, "Speed");
DBusNMWiredProxy* wiredProxy = nullptr;
};
}; // namespace qs::network
+28 -291
View File
@@ -1,5 +1,4 @@
#include "wireless.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qdatetime.h>
@@ -12,7 +11,6 @@
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpointer.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -28,6 +26,7 @@
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
#include "utils.hpp"
@@ -38,143 +37,6 @@ namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent)
: QObject(parent)
, mSsid(std::move(ssid))
, bKnown(false)
, bSecurity(WifiSecurityType::Unknown)
, bReason(NMConnectionStateReason::None)
, bState(NMConnectionState::Deactivated) {}
void NMWirelessNetwork::updateReferenceSettings() {
// If the network has no connections, the reference is nullptr.
if (this->mSettings.isEmpty()) {
this->mReferenceSettings = nullptr;
this->bSecurity = WifiSecurityType::Unknown;
if (this->mReferenceAp) {
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
}
return;
};
// If the network has an active connection, use its settings as the reference.
if (this->mActiveConnection) {
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
if (settings && settings != this->mReferenceSettings) {
this->mReferenceSettings = settings;
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
}
return;
}
// Otherwise, choose the settings responsible for the last successful connection.
NMSettings* selectedSettings = nullptr;
quint64 selectedTimestamp = 0;
for (auto* settings: this->mSettings.values()) {
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
if (!selectedSettings || timestamp > selectedTimestamp) {
selectedSettings = settings;
selectedTimestamp = timestamp;
}
}
if (this->mReferenceSettings != selectedSettings) {
this->mReferenceSettings = selectedSettings;
this->bSecurity.setBinding([selectedSettings]() {
return securityFromSettingsMap(selectedSettings->map());
});
}
}
void NMWirelessNetwork::updateReferenceAp() {
// If the network has no APs, the reference is a nullptr.
if (this->mAccessPoints.isEmpty()) {
this->mReferenceAp = nullptr;
this->bSignalStrength = 0;
return;
}
// Otherwise, choose the AP with the strongest signal.
NMAccessPoint* selectedAp = nullptr;
for (auto* ap: this->mAccessPoints.values()) {
// Always prefer the active AP.
if (ap->path() == this->bActiveApPath) {
selectedAp = ap;
break;
}
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
selectedAp = ap;
}
}
if (this->mReferenceAp != selectedAp) {
this->mReferenceAp = selectedAp;
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
// Reference AP is used for security when there's no connection settings.
if (!this->mReferenceSettings) {
this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); });
}
}
}
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
if (this->mAccessPoints.contains(ap->path())) return;
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
this->updateReferenceAp();
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
// clang-format off
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
// clang-format on
this->updateReferenceAp();
};
void NMWirelessNetwork::addSettings(NMSettings* settings) {
if (this->mSettings.contains(settings->path())) return;
this->mSettings.insert(settings->path(), settings);
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
this->bKnown = true;
this->updateReferenceSettings();
emit this->settingsAdded(settings);
};
void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) {
if (this->mActiveConnection) return;
this->mActiveConnection = active;
this->bState.setBinding([active]() { return active->state(); });
this->bReason.setBinding([active]() { return active->stateReason(); });
auto onDestroyed = [this, active]() {
if (this->mActiveConnection && this->mActiveConnection == active) {
this->mActiveConnection = nullptr;
this->updateReferenceSettings();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceSettings();
};
void NMWirelessNetwork::forget() {
if (this->mSettings.isEmpty()) return;
for (auto* conn: this->mSettings.values()) {
conn->forget();
}
}
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
: NMDevice(path, parent)
, mScanTimer(this) {
@@ -203,6 +65,10 @@ NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
this->wirelessProperties.setInterface(this->wirelessProxy);
this->wirelessProperties.updateAllViaGetAll();
// Register and bind the frontend WifiDevice.
this->mFrontend = new WifiDevice(this);
this->bindFrontend();
}
void NMWirelessDevice::initWireless() {
@@ -214,7 +80,9 @@ void NMWirelessDevice::initWireless() {
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged);
// clang-format on
this->registerAccessPoints();
emit this->loaded();
}
void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) {
@@ -271,7 +139,7 @@ void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
}
void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
// Find an exisiting network with connection settings that matches the active
// Find an existing network with connection settings that matches the active
const QString activeConnPath = active->connection().path();
for (const auto& net: this->mNetworks.values()) {
for (auto* settings: net->settings()) {
@@ -364,176 +232,45 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
}
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
auto* net = new NMWirelessNetwork(ssid, this);
auto* net = new NMWirelessNetwork(ssid, this->frontend(), this);
this->NMDevice::bindNetwork(net);
auto visible = [this, net]() {
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
};
net->bindableVisible().setBinding(visible);
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
this->mNetworks.insert(ssid, net);
this->registerFrontendNetwork(net);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
return net;
}
void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) {
auto ssid = net->ssid();
auto* frontendNet = new WifiNetwork(ssid, net);
// Bind WifiNetwork to NMWirelessNetwork
auto translateSignal = [net]() { return net->signalStrength() / 100.0; };
auto translateState = [net]() { return net->state() == NMConnectionState::Activated; };
frontendNet->bindableSignalStrength().setBinding(translateSignal);
frontendNet->bindableConnected().setBinding(translateState);
frontendNet->bindableKnown().setBinding([net]() { return net->known(); });
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
frontendNet->bindableState().setBinding([net]() {
return static_cast<ConnectionState::Enum>(net->state());
});
QObject::connect(net, &NMWirelessNetwork::reasonChanged, this, [net, frontendNet]() {
if (net->reason() == NMConnectionStateReason::DeviceDisconnected) {
auto deviceReason = net->deviceFailReason();
if (deviceReason == NMDeviceStateReason::NoSecrets)
emit frontendNet->connectionFailed(ConnectionFailReason::NoSecrets);
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientFailed);
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
if (deviceReason == NMDeviceStateReason::SsidNotFound)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiNetworkLost);
}
});
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
if (net->referenceSettings()) {
emit this->activateConnection(
QDBusObjectPath(net->referenceSettings()->path()),
QDBusObjectPath(this->path())
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
NMSettingsMap(),
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connect to"
<< this->path() + ": The network disappeared.";
});
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithPsk,
this,
[this, net](const QString& psk) {
NMSettingsMap settings;
settings["802-11-wireless-security"]["psk"] = psk;
if (const QPointer<NMSettings> ref = net->referenceSettings()) {
auto* call = ref->updateSettings(settings);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this, ref](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager)
<< "Failed to write PSK for" << this->path() + ":" << reply.error().message();
} else {
if (!ref) {
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The settings disappeared.";
} else {
emit this->activateConnection(
QDBusObjectPath(ref->path()),
QDBusObjectPath(this->path())
);
}
}
delete call;
}
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
settings,
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The network disappeared.";
}
);
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithSettings,
this,
[this](NMSettings* settings) {
if (settings) {
emit this->activateConnection(
QDBusObjectPath(settings->path()),
QDBusObjectPath(this->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithSettings to"
<< this->path() + ": The provided settings no longer exist.";
}
);
QObject::connect(
net,
&NMWirelessNetwork::visibilityChanged,
this,
[this, frontendNet](bool visible) {
if (visible) this->networkAdded(frontendNet);
else this->networkRemoved(frontendNet);
}
);
// clang-format off
QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect);
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
QObject::connect(net, &NMWirelessNetwork::settingsAdded, frontendNet, &WifiNetwork::settingsAdded);
QObject::connect(net, &NMWirelessNetwork::settingsRemoved, frontendNet, &WifiNetwork::settingsRemoved);
// clang-format on
this->mFrontendNetworks.insert(ssid, frontendNet);
if (net->visible()) emit this->networkAdded(frontendNet);
}
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
if (frontendNet) {
if (net->visible()) emit this->networkRemoved(frontendNet);
frontendNet->deleteLater();
}
}
void NMWirelessDevice::removeNetwork() {
auto* net = qobject_cast<NMWirelessNetwork*>(this->sender());
if (this->mNetworks.take(net->ssid())) {
this->removeFrontendNetwork(net);
if (net->visible()) emit this->networkRemoved(net->frontend());
delete net;
};
}
void NMWirelessDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
auto translateMode = [this]() {
switch (this->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
frontend->bindableMode().setBinding(translateMode);
this->bindableScanning().setBinding([frontend]() { return frontend->scannerEnabled(); });
}
bool NMWirelessDevice::isValid() const {
return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid());
}
+5 -73
View File
@@ -13,6 +13,7 @@
#include "dbus_nm_wireless.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@@ -33,76 +34,9 @@ struct DBusDataTransform<QDateTime> {
} // namespace qs::dbus
namespace qs::network {
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMSettings objects.
class NMWirelessNetwork: public QObject {
Q_OBJECT;
public:
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->mReferenceSettings; }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
QBindable<bool> bindableVisible() { return &this->bVisible; }
bool visible() const { return this->bVisible; }
// clang-format on
signals:
void disappeared();
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void visibilityChanged(bool visible);
void signalStrengthChanged(quint8 signal);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void securityChanged(WifiSecurityType::Enum security);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void activeApPathChanged(QString path);
private:
void updateReferenceAp();
void updateReferenceSettings();
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMSettings*> mSettings;
NMAccessPoint* mReferenceAp = nullptr;
NMSettings* mReferenceSettings = nullptr;
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMWirelessNetwork::deviceFailReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
// clang-format on
};
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s).
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), and a frontend WifiDevice.
class NMWirelessDevice: public NMDevice {
Q_OBJECT;
@@ -114,12 +48,11 @@ public:
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; }
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; }
[[nodiscard]] WifiDevice* frontend() override { return this->mFrontend; };
signals:
void accessPointLoaded(NMAccessPoint* ap);
void accessPointRemoved(NMAccessPoint* ap);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
void lastScanChanged(QDateTime lastScan);
void scanningChanged(bool scanning);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
@@ -137,17 +70,16 @@ private slots:
private:
void registerAccessPoint(const QString& path);
void registerFrontendNetwork(NMWirelessNetwork* net);
void removeFrontendNetwork(NMWirelessNetwork* net);
void removeNetwork();
bool checkVisibility(WifiNetwork* net);
void registerAccessPoints();
void initWireless();
void bindFrontend();
NMWirelessNetwork* registerNetwork(const QString& ssid);
WifiDevice* mFrontend;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMWirelessNetwork*> mNetworks;
QHash<QString, WifiNetwork*> mFrontendNetworks;
QDateTime mLastScanRequest;
QTimer mScanTimer;
+82
View File
@@ -0,0 +1,82 @@
#include "qml.hpp"
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
} // namespace qs::network
+151
View File
@@ -0,0 +1,151 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
namespace qs::network {
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
} // namespace qs::network
+240 -213
View File
@@ -73,6 +73,241 @@ Scope {
}
}
Component {
id: deviceDelegate
WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
RowLayout {
visible: modelData.type === DeviceType.Wired
CheckBox {
text: "Link connected"
checked: modelData.hasLink
enabled: false
}
Label {
text: `Link max speed: ${modelData.linkSpeed} Mbps`
}
}
RowLayout {
visible: modelData.type === DeviceType.Wifi
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
}
Repeater {
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
if (modelData.device?.type === DeviceType.Wifi) {
return b.signalStrength - a.signalStrength;
}
})
}
WrapperRectangle {
id: ethernetNetwork
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
visible: modelData.device?.type === DeviceType.Wifi
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (ethernetNetwork.chosenSettings)
modelData.connectWithSettings(ethernetNetwork.chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (ethernetNetwork.chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
}
}
}
FloatingWindow {
color: contentItem.palette.window
@@ -139,219 +374,11 @@ Scope {
Layout.fillWidth: true
Layout.fillHeight: true
model: Networking.devices
delegate: WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
Repeater {
Layout.fillWidth: true
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
return b.signalStrength - a.signalStrength;
})
}
WrapperRectangle {
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (chosenSettings)
modelData.connectWithSettings(chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
delegate: Component {
Loader {
required property var modelData
width: ListView.view.width
sourceComponent: deviceDelegate
}
}
}
+2 -4
View File
@@ -19,7 +19,8 @@ namespace {
QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg);
}
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent)
: Network(std::move(ssid), device, parent) {};
void WifiNetwork::connectWithPsk(const QString& psk) {
if (this->bConnected) {
@@ -42,9 +43,6 @@ void WifiDevice::setScannerEnabled(bool enabled) {
this->bScannerEnabled = enabled;
}
void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); }
void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) {
+2 -11
View File
@@ -7,7 +7,6 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
@@ -27,7 +26,7 @@ class WifiNetwork: public Network {
// clang-format on
public:
explicit WifiNetwork(QString ssid, QObject* parent = nullptr);
explicit WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network with the given PSK. If the PSK is wrong,
/// a @@Network.connectionFailed(s) signal will be emitted with `NoSecrets`.
///
@@ -61,9 +60,6 @@ class WifiDevice: public NetworkDevice {
QML_UNCREATABLE("");
// clang-format off
/// A list of this available or connected wifi networks.
QSDOC_TYPE_OVERRIDE(ObjectModel<WifiNetwork>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// True when currently scanning for networks.
/// When enabled, the scanner populates the device with an active list of available wifi networks.
Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled);
@@ -74,10 +70,6 @@ class WifiDevice: public NetworkDevice {
public:
explicit WifiDevice(QObject* parent = nullptr);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
[[nodiscard]] ObjectModel<WifiNetwork>* networks() { return &this->mNetworks; }
QBindable<bool> bindableScannerEnabled() { return &this->bScannerEnabled; }
[[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; }
void setScannerEnabled(bool enabled);
@@ -88,12 +80,11 @@ signals:
void scannerEnabledChanged(bool enabled);
private:
ObjectModel<WifiNetwork> mNetworks {this};
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged);
};
}; // namespace qs::network
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network);
QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device);
+22
View File
@@ -0,0 +1,22 @@
#include "wired.hpp"
#include <qobject.h>
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
WiredDevice::WiredDevice(QObject* parent): NetworkDevice(DeviceType::Wired, parent) {}
void WiredDevice::networkAdded(Network* net) {
this->NetworkDevice::networkAdded(net);
this->bNetwork = net;
};
void WiredDevice::networkRemoved(Network* net) {
this->NetworkDevice::networkRemoved(net);
this->bNetwork = nullptr;
};
} // namespace qs::network
+53
View File
@@ -0,0 +1,53 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "device.hpp"
#include "network.hpp"
namespace qs::network {
///! Wired variant of a @@NetworkDevice.
class WiredDevice: public NetworkDevice {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("");
/// The wired network for this device or `null`.
///
/// > [!NOTE] This network is only available when @@hasLink is `true`.
Q_PROPERTY(Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork);
/// The maximum speed of the physical device link, in megabits per second.
Q_PROPERTY(quint32 linkSpeed READ default NOTIFY linkSpeedChanged BINDABLE bindableLinkSpeed);
/// True if the wired device has a physical link (cable plugged in).
Q_PROPERTY(bool hasLink READ default NOTIFY hasLinkChanged BINDABLE bindableHasLink);
public:
explicit WiredDevice(QObject* parent = nullptr);
void networkAdded(Network* net) override;
void networkRemoved(Network* net) override;
[[nodiscard]] Network* network() const { return this->bNetwork; }
QBindable<Network*> bindableNetwork() { return &this->bNetwork; }
QBindable<quint32> bindableLinkSpeed() { return &this->bLinkSpeed; }
QBindable<bool> bindableHasLink() { return &this->bHasLink; }
signals:
void networkChanged();
void linkSpeedChanged();
void hasLinkChanged();
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, Network*, bNetwork, &WiredDevice::networkChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, quint32, bLinkSpeed, &WiredDevice::linkSpeedChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, bool, bHasLink, &WiredDevice::hasLinkChanged);
// clang-format on
};
} // namespace qs::network
+41 -2
View File
@@ -193,7 +193,7 @@ void PwNode::initProps(const spa_dict* props) {
qCCritical(
logNode
) << this
<< "has a device.id property that does not corrospond to a device object. Id:" << id;
<< "has a device.id property that does not correspond to a device object. Id:" << id;
}
}
}
@@ -580,7 +580,7 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
spa_pod* iter = nullptr;
SPA_POD_ARRAY_FOREACH(volumes, iter) {
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
// Cubing behavior found in MPD source, and appears to correspond to everyone else's measurements correctly.
auto linear = *reinterpret_cast<float*>(iter);
auto visual = std::cbrt(linear);
props.volumes.push_back(visual);
@@ -595,6 +595,45 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
}
}
if (props.channels.isEmpty()) {
// See spa/param/audio/layout.h and pw utils.
using C = PwAudioChannel;
// clang-format off
switch (props.volumes.length()) {
case 1: props.channels = {C::Mono}; break;
case 2: props.channels = {C::FrontLeft, C::FrontRight}; break;
case 3: props.channels = {C::FrontLeft, C::FrontRight, C::LowFrequencyEffects}; break;
case 4: props.channels = {C::FrontLeft, C::FrontRight, C::RearLeft, C::RearRight}; break;
case 5:
props.channels = {C::FrontLeft, C::FrontRight, C::FrontCenter, C::SideLeft, C::SideRight};
break;
case 6:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::LowFrequencyEffects,
C::SideLeft, C::SideRight
};
break;
case 7:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::RearLeft, C::RearRight,
C::SideLeft, C::SideRight
};
break;
case 8:
props.channels = {
C::FrontLeft, C::FrontRight, C::FrontCenter,
C::LowFrequencyEffects,
C::RearLeft, C::RearRight,
C::SideLeft, C::SideRight
};
break;
default: break;
}
// clang-format on
}
if (muteProp) {
spa_pod_get_bool(&muteProp->value, &props.mute);
}
+1 -1
View File
@@ -140,7 +140,7 @@ public:
// This is equivalent to the media class `Video/Source` and is composed
// of the @@PwNodeType.Video and @@PwNodeType.Source flags.
VideoSource = Video | Source,
// A consumer of video, such as a program that is recieving a video stream.
// A consumer of video, such as a program that is receiving a video stream.
//
// This is equivalent to the media class `Video/Sink` and is composed of the
// @@PwNodeType.Video and @@PwNodeType.Sink flags.
+4 -4
View File
@@ -233,7 +233,7 @@ class PwNodeAudioIface: public QObject {
///
/// > [!WARNING] This property is invalid unless the node is bound using @@PwObjectTracker.
Q_PROPERTY(QVector<qs::service::pipewire::PwAudioChannel::Enum> channels READ channels NOTIFY channelsChanged);
/// The volumes of each audio channel individually. Each entry corrosponds to
/// The volumes of each audio channel individually. Each entry corresponds to
/// the volume of the channel at the same index in @@channels. @@volumes and @@channels
/// will always be the same length.
///
@@ -274,13 +274,13 @@ class PwNodeIface: public PwObjectIface {
/// Mainly useful for debugging. You can inspect the node directly
/// with `pw-cli i <id>`.
Q_PROPERTY(quint32 id READ id CONSTANT);
/// The node's name, corrosponding to the object's `node.name` property.
/// The node's name, corresponding to the object's `node.name` property.
Q_PROPERTY(QString name READ name CONSTANT);
/// The node's description, corrosponding to the object's `node.description` property.
/// The node's description, corresponding to the object's `node.description` property.
///
/// May be empty. Generally more human readable than @@name.
Q_PROPERTY(QString description READ description CONSTANT);
/// The node's nickname, corrosponding to the object's `node.nickname` property.
/// The node's nickname, corresponding to the object's `node.nickname` property.
///
/// May be empty. Generally but not always more human readable than @@description.
Q_PROPERTY(QString nickname READ nickname CONSTANT);
+2 -2
View File
@@ -127,14 +127,14 @@ signals:
///
/// This may be because the user entered the wrong password or otherwise
/// failed to authenticate.
/// This signal is not emmitted when the user canceled the request or it
/// This signal is not emitted when the user canceled the request or it
/// was cancelled by the PolKit daemon.
///
/// After this signal, a new session is automatically started for the same
/// identity.
void authenticationFailed();
/// Emmitted when on ongoing authentication request is cancelled by the PolKit daemon.
/// Emitted when an ongoing authentication request is cancelled by the PolKit daemon.
void authenticationRequestCancelled();
void selectedIdentityChanged();
+1 -1
View File
@@ -41,7 +41,7 @@ A conversation can take multiple turns, for example if second factors are involv
If authentication fails, we automatically create a fresh session so the user can try again.
The @@Quickshell.Services.Polkit.AuthFlow.authenticationFailed signal is emitted in this case.
If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeeded signal. At this point, the dialog can be closed.
If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeded signal. At this point, the dialog can be closed.
If additional requests are queued, you will receive the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal again.
#### Cancelled requests
+429 -122
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,106 +316,329 @@ 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 = {}
};
instFuncs->vkGetPhysicalDeviceProperties2(physDevice, &props2);
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 {};
};
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 {
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 freeDrmDev = qScopeGuard([&] { drmFreeDevice(&drmDev); });
std::string renderNodeStorage;
std::string* renderNode = nullptr;
auto sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.handle == handle;
});
if (sharedDevice != this->gbmDevices.end()) {
renderNode = &sharedDevice->renderNode;
} else {
drmDevice* drmDevPtr = nullptr;
if (auto error = drmGetDeviceFromDevId(handle, 0, &drmDevPtr); error != 0) {
qCWarning(logDmabuf) << "Failed to get drm device information from handle:"
<< qt_error_string(error);
return nullptr;
}
auto drmDev = QScopedPointer<drmDevice, DrmFree>(drmDevPtr);
if (!(drmDev->available_nodes & (1 << DRM_NODE_RENDER))) {
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
return nullptr;
}
if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) {
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
renderNode = &renderNodeStorage;
sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
return d.renderNode == renderNodeStorage;
});
}
if (sharedDevice != this->gbmDevices.end()) {
qCDebug(logDmabuf) << "Used existing GBM device on render node" << *renderNode;
++sharedDevice->refcount;
return sharedDevice->device;
} else if (drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)) {
renderNodeStorage = drmDev->nodes[DRM_NODE_PRIMARY]; // NOLINT
} else {
auto fd = open(renderNode->c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
qCDebug(logDmabuf) << "Could not open render node" << *renderNode << ":"
<< qt_error_string(fd);
return nullptr;
}
auto* device = gbm_create_device(fd);
if (!device) {
qCDebug(logDmabuf) << "Failed to create GBM device from render node" << *renderNode;
close(fd);
return nullptr;
}
qCDebug(logDmabuf) << "Created GBM device on render node" << *renderNode;
this->gbmDevices.push_back({
.handle = handle,
.renderNode = std::move(renderNodeStorage),
.device = device,
.refcount = 1,
});
return device;
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
return nullptr;
}
}
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;
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;
}
}
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
auto fd = open(renderNodeStorage.c_str(), O_RDWR | O_CLOEXEC);
if (fd < 0) {
qCDebug(logDmabuf) << "Could not open render node" << renderNodeStorage.c_str() << ":"
<< qt_error_string(fd);
return nullptr;
}
if (--iter->refcount == 0) {
auto fd = gbm_device_get_fd(iter->device);
gbm_device_destroy(iter->device);
auto* device = gbm_create_device(fd);
if (!device) {
qCDebug(logDmabuf) << "Failed to create GBM device from render node"
<< renderNodeStorage.c_str();
close(fd);
this->gbmDevices.erase(iter);
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
return nullptr;
}
}
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
if (!handle) return GbmDeviceHandle();
qCDebug(logDmabuf) << "Created GBM device on render node" << renderNodeStorage.c_str();
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,16 +648,67 @@ 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) {
+22 -34
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
+61 -10
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)
+4
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);
+3 -1
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
+17 -4
View File
@@ -74,10 +74,23 @@ HyprlandIpc::HyprlandIpc() {
// clang-format on
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
this->refreshMonitors(true);
this->refreshWorkspaces(true);
this->refreshToplevels();
this->makeRequest("j/status", [&, this](bool success, QByteArray resp) {
if (success) {
this->bUsingLua = [&]() {
if (resp == "unknown request") return false;
auto json = QJsonDocument::fromJson(resp).object();
auto provider = json.value("configProvider");
return provider == "lua";
}();
} else {
qCWarning(logHyprlandIpc) << "Hyprland ipc status request failed.";
}
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
this->refreshMonitors(true);
this->refreshWorkspaces(true);
this->refreshToplevels();
});
}
QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; }
+5
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*,
+4
View File
@@ -51,6 +51,10 @@ void HyprlandIpcQml::refreshToplevels() { HyprlandIpc::instance()->refreshToplev
QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->requestSocketPath(); }
QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); }
QBindable<bool> HyprlandIpcQml::bindableUsingLua() {
return HyprlandIpc::instance()->bindableUsingLua();
}
QBindable<HyprlandMonitor*> HyprlandIpcQml::bindableFocusedMonitor() {
return HyprlandIpc::instance()->bindableFocusedMonitor();
}
+7 -1
View File
@@ -16,6 +16,10 @@ namespace qs::hyprland::ipc {
class HyprlandIpcQml: public QObject {
Q_OBJECT;
// clang-format off
/// True if Hyprland is running in lua mode. Dispatcher syntax changes when using lua.
///
/// This property will be false until the Hyprland module is initialized.
Q_PROPERTY(bool usingLua READ default NOTIFY usingLuaChanged BINDABLE bindableUsingLua);
/// Path to the request socket (.socket.sock)
Q_PROPERTY(QString requestSocketPath READ requestSocketPath CONSTANT);
/// Path to the event socket (.socket2.sock)
@@ -47,7 +51,7 @@ public:
/// Execute a hyprland [dispatcher](https://wiki.hyprland.org/Configuring/Dispatchers).
Q_INVOKABLE static void dispatch(const QString& request);
/// Get the HyprlandMonitor object that corrosponds to a quickshell screen.
/// Get the HyprlandMonitor object that corresponds to a quickshell screen.
Q_INVOKABLE static HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
/// Refresh monitor information.
@@ -70,6 +74,7 @@ public:
[[nodiscard]] static QString requestSocketPath();
[[nodiscard]] static QString eventSocketPath();
[[nodiscard]] static QBindable<bool> bindableUsingLua();
[[nodiscard]] static QBindable<HyprlandMonitor*> bindableFocusedMonitor();
[[nodiscard]] static QBindable<HyprlandWorkspace*> bindableFocusedWorkspace();
[[nodiscard]] static QBindable<HyprlandToplevel*> bindableActiveToplevel();
@@ -83,6 +88,7 @@ signals:
/// See [Hyprland Wiki: IPC](https://wiki.hyprland.org/IPC/) for a list of events.
void rawEvent(qs::hyprland::ipc::HyprlandIpcEvent* event);
void usingLuaChanged();
void focusedMonitorChanged();
void focusedWorkspaceChanged();
void activeToplevelChanged();
+14 -1
View File
@@ -151,7 +151,20 @@ void HyprlandWorkspace::clearUrgent() {
}
void HyprlandWorkspace::activate() {
this->ipc->dispatch(QString("workspace %1").arg(this->bName.value()));
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
+16
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
+3
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;
+1 -1
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());
@@ -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);
+2
View File
@@ -141,6 +141,8 @@ void ScreencopyView::componentComplete() {
this,
&ScreencopyView::onBuffersReady
);
bufManager->initWindow(this->window());
} else {
this->onBuffersReady();
}
+2 -2
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.
+2 -2
View File
@@ -31,7 +31,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla
if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) {
output = waylandScreen->output();
} else {
qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window";
qFatal() << "Session lock screen does not correspond to a real screen. Force closing window";
}
this->init(this->ext->lock->get_lock_surface(window->waylandSurface()->object(), output));
@@ -146,7 +146,7 @@ void QSWaylandSessionLockSurface::initVisible() {
#include <qtclasshelpermacros.h>
#include <qtdeprecationdefinitions.h>
// As of Qt 6.9, a null buffer is unconditionally comitted to the surface. To avoid this, we
// As of Qt 6.9, a null buffer is unconditionally committed to the surface. To avoid this, we
// cast the window to a subclass with access to mSurface, then swap mSurface during
// QWaylandWindow::initWindow to avoid the null commit.
@@ -109,7 +109,7 @@ bool WlrLayershell::deleteOnInvisible() const {
// Qt windows behave weirdly when geometry is modified and setVisible(false)
// is subsequently called in the same frame.
// It will attach buffers to the wayland surface unconditionally before
// the surface recieves a configure event, causing a protocol error.
// the surface receives a configure event, causing a protocol error.
// To remedy this we forcibly disallow window reuse.
return true;
}
+1 -1
View File
@@ -46,7 +46,7 @@ Item {
/// exposed by IconImage.
property /*Image*/alias backer: image
/// The suggested size of the image. This is used as a defualt
/// The suggested size of the image. This is used as a default
/// for @@QtQuick.Item.implicitWidth and @@QtQuick.Item.implicitHeight.
property real implicitSize: 0
+4 -4
View File
@@ -65,7 +65,7 @@ enum Enum : quint8 {
Ignore = 1,
/// Decide the exclusion zone based on the window dimensions and anchors.
///
/// Will attempt to reseve exactly enough space for the window and its margins if
/// Will attempt to reserve exactly enough space for the window and its margins if
/// exactly 3 anchors are connected.
Auto = 2,
};
@@ -99,7 +99,7 @@ class PanelWindowInterface: public WindowInterface {
/// Anchors attach a shell window to the sides of the screen.
/// By default all anchors are disabled to avoid blocking the entire screen due to a misconfiguration.
///
/// > [!INFO] When two opposite anchors are attached at the same time, the corrosponding dimension
/// > [!INFO] When two opposite anchors are attached at the same time, the corresponding dimension
/// > (width or height) will be forced to equal the screen width/height.
/// > Margins can be used to create anchored windows that are also disconnected from the monitor sides.
Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged);
@@ -116,11 +116,11 @@ class PanelWindowInterface: public WindowInterface {
Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged);
/// If the panel should render above standard windows. Defaults to true.
///
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.layer.
/// Note: On Wayland this property corresponds to @@Quickshell.Wayland.WlrLayershell.layer.
Q_PROPERTY(bool aboveWindows READ aboveWindows WRITE setAboveWindows NOTIFY aboveWindowsChanged);
/// If the panel should accept keyboard focus. Defaults to false.
///
/// Note: On Wayland this property corrosponds to @@Quickshell.Wayland.WlrLayershell.keyboardFocus.
/// Note: On Wayland this property corresponds to @@Quickshell.Wayland.WlrLayershell.keyboardFocus.
Q_PROPERTY(bool focusable READ focusable WRITE setFocusable NOTIFY focusableChanged);
// clang-format on
QML_NAMED_ELEMENT(PanelWindow);
+29 -1
View File
@@ -1,4 +1,5 @@
#include "proxywindow.hpp"
#include <functional>
#include <private/qquickwindow_p.h>
#include <qcontainerfwd.h>
@@ -242,7 +243,7 @@ void ProxyWindowBase::completeWindow() {
this->mContentItem->setWidth(this->width());
this->mContentItem->setHeight(this->height());
// without this the dangling screen pointer wont be updated to a real screen
// without this the dangling screen pointer won't be updated to a real screen
emit this->screenChanged();
}
@@ -611,6 +612,33 @@ void ProxyWindowAttached::setWindow(ProxyWindowBase* window) {
emit this->windowChanged();
}
ProxiedWindow::ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent)
: QQuickWindow(parent)
, mProxy(proxy) {
QObject::connect(
this,
&QQuickWindow::sceneGraphInitialized,
this,
&ProxiedWindow::onSceneGraphInitialized
);
}
namespace {
QList<std::function<void(QQuickWindow*)>> SCENEGRAPH_INIT_CALLBACKS; // NOLINT
}
void ProxiedWindow::callOnScenegraphInit(std::function<void(QQuickWindow*)> cb) { // NOLINT
SCENEGRAPH_INIT_CALLBACKS.emplaceBack(cb);
}
void ProxiedWindow::onSceneGraphInitialized() {
for (auto& cb: SCENEGRAPH_INIT_CALLBACKS) {
cb(this);
}
SCENEGRAPH_INIT_CALLBACKS.clear();
}
bool ProxiedWindow::event(QEvent* event) {
if (event->type() == QEvent::DevicePixelRatioChange) {
emit this->devicePixelRatioChanged();
+8 -3
View File
@@ -1,5 +1,7 @@
#pragma once
#include <functional>
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qevent.h>
@@ -254,17 +256,20 @@ class ProxiedWindow: public QQuickWindow {
Q_OBJECT;
public:
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr)
: QQuickWindow(parent)
, mProxy(proxy) {}
explicit ProxiedWindow(ProxyWindowBase* proxy, QWindow* parent = nullptr);
[[nodiscard]] ProxyWindowBase* proxy() const { return this->mProxy; }
void setProxy(ProxyWindowBase* proxy) { this->mProxy = proxy; }
static void callOnScenegraphInit(std::function<void(QQuickWindow*)> cb);
signals:
void exposed();
void devicePixelRatioChanged();
private slots:
void onSceneGraphInitialized();
protected:
bool event(QEvent* event) override;
void exposeEvent(QExposeEvent* event) override;
+1 -1
View File
@@ -133,7 +133,7 @@ void I3Ipc::eventSocketReady() {
void I3Ipc::connect() { this->liveEventSocket.connectToServer(this->mSocketPath); }
void I3Ipc::reconnectIPC() {
qCWarning(logI3Ipc) << "Fatal IPC error occured, recreating connection";
qCWarning(logI3Ipc) << "Fatal IPC error occurred, recreating connection";
this->liveEventSocket.disconnectFromServer();
this->connect();
}
+3 -9
View File
@@ -224,13 +224,13 @@ void I3IpcController::handleRunCommand(I3IpcEvent* event) {
if (!success) {
const QString error = obj["error"].toString();
qCWarning(logI3Ipc) << "Error occured while running command:" << error;
qCWarning(logI3Ipc) << "Error occurred while running command:" << error;
}
}
}
void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) {
// If a workspace doesn't exist, and is being switch to, no focus change event is emited,
// If a workspace doesn't exist, and is being switch to, no focus change event is emitted,
// only the init one, which does not contain the previously focused workspace
auto change = event->mData["change"];
@@ -276,7 +276,7 @@ void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) {
if (newWorkspace->bindableMonitor().value()) {
auto* monitor = newWorkspace->bindableMonitor().value();
monitor->setFocusedWorkspace(newWorkspace);
monitor->setActiveWorkspace(newWorkspace);
this->bFocusedMonitor = monitor;
}
} else if (change == "empty") {
@@ -286,13 +286,7 @@ void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) {
if (oldWorkspace != nullptr) {
qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name;
if (this->bFocusedWorkspace == oldWorkspace) {
this->bFocusedMonitor->setFocusedWorkspace(nullptr);
}
this->workspaces()->removeObject(oldWorkspace);
delete oldWorkspace;
} else {
qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted";
+22 -6
View File
@@ -40,21 +40,37 @@ void I3Monitor::updateFromObject(const QVariantMap& obj) {
this->bHeight = rect.value("height").value<qint32>();
this->bScale = obj.value("scale").value<qreal>();
if (!this->bActiveWorkspace
|| activeWorkspaceName != this->bActiveWorkspace->bindableName().value())
{
auto* activeWorkspace = this->bActiveWorkspace.value();
if (!activeWorkspace || activeWorkspaceName != activeWorkspace->bindableName().value()) {
if (activeWorkspaceName.isEmpty()) {
this->bActiveWorkspace = nullptr;
activeWorkspace = nullptr;
} else {
this->bActiveWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName);
activeWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName);
}
};
this->setActiveWorkspace(activeWorkspace);
Qt::endPropertyUpdateGroup();
}
void I3Monitor::updateInitial(const QString& name) { this->bName = name; }
void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) { this->bActiveWorkspace = workspace; };
void I3Monitor::setActiveWorkspace(I3Workspace* workspace) {
auto* oldWorkspace = this->bActiveWorkspace.value();
if (oldWorkspace == workspace) return;
if (oldWorkspace) {
QObject::disconnect(oldWorkspace, nullptr, this, nullptr);
}
if (workspace) {
QObject::connect(workspace, &QObject::destroyed, this, &I3Monitor::onActiveWorkspaceDestroyed);
}
this->bActiveWorkspace = workspace;
}
void I3Monitor::onActiveWorkspaceDestroyed() { this->bActiveWorkspace = nullptr; }
} // namespace qs::i3::ipc
+6 -3
View File
@@ -16,7 +16,7 @@ class I3Monitor: public QObject {
Q_PROPERTY(qint32 id READ default NOTIFY idChanged BINDABLE bindableId);
/// The name of this monitor
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// Wether this monitor is turned on or not
/// Whether this monitor is turned on or not
Q_PROPERTY(bool power READ default NOTIFY powerChanged BINDABLE bindablePower);
/// The currently active workspace on this monitor, May be null.
Q_PROPERTY(qs::i3::ipc::I3Workspace* activeWorkspace READ default NOTIFY activeWorkspaceChanged BINDABLE bindableActiveWorkspace);
@@ -55,7 +55,7 @@ public:
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() {
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() const {
return &this->bActiveWorkspace;
}
@@ -64,7 +64,7 @@ public:
void updateFromObject(const QVariantMap& obj);
void updateInitial(const QString& name);
void setFocusedWorkspace(I3Workspace* workspace);
void setActiveWorkspace(I3Workspace* workspace);
signals:
void idChanged();
@@ -79,6 +79,9 @@ signals:
void lastIpcObjectChanged();
void focusedChanged();
private slots:
void onActiveWorkspaceDestroyed();
private:
I3IpcController* ipc;
+23 -3
View File
@@ -43,14 +43,17 @@ void I3Workspace::updateFromObject(const QVariantMap& obj) {
auto monitorName = obj.value("output").value<QString>();
if (!this->bMonitor || monitorName != this->bMonitor->bindableName().value()) {
auto* monitor = this->bMonitor.value();
if (!monitor || monitorName != monitor->bindableName().value()) {
if (monitorName.isEmpty()) {
this->bMonitor = nullptr;
monitor = nullptr;
} else {
this->bMonitor = this->ipc->findMonitorByName(monitorName, true);
monitor = this->ipc->findMonitorByName(monitorName, true);
}
}
this->setMonitor(monitor);
Qt::endPropertyUpdateGroup();
}
@@ -58,4 +61,21 @@ void I3Workspace::activate() {
this->ipc->dispatch(QString("workspace number %1").arg(this->bNumber.value()));
}
void I3Workspace::setMonitor(I3Monitor* monitor) {
auto* oldMonitor = this->bMonitor.value();
if (oldMonitor == monitor) return;
if (oldMonitor) {
QObject::disconnect(oldMonitor, nullptr, this, nullptr);
}
if (monitor) {
QObject::connect(monitor, &QObject::destroyed, this, &I3Workspace::onMonitorDestroyed);
}
this->bMonitor = monitor;
}
void I3Workspace::onMonitorDestroyed() { this->bMonitor = nullptr; }
} // namespace qs::i3::ipc
+6 -1
View File
@@ -57,11 +57,13 @@ public:
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() const { return &this->bMonitor; }
[[nodiscard]] QVariantMap lastIpcObject() const;
void updateFromObject(const QVariantMap& obj);
void setMonitor(I3Monitor* monitor);
signals:
void idChanged();
void nameChanged();
@@ -72,6 +74,9 @@ signals:
void monitorChanged();
void lastIpcObjectChanged();
private slots:
void onMonitorDestroyed();
private:
I3IpcController* ipc;