mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-28 02:55:05 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56633822be |
No files matched your search
@@ -13,11 +13,17 @@ body:
|
||||
id: report
|
||||
attributes:
|
||||
label: Report file
|
||||
description: Attach `report.txt` here.
|
||||
validations:
|
||||
required: true
|
||||
- type: upload
|
||||
id: logs
|
||||
attributes:
|
||||
label: Log file
|
||||
description: |
|
||||
Attach `report.txt` here.
|
||||
Attach `log.qslog.log` here. If it is too big to upload, compress it.
|
||||
|
||||
Please ensure the report does not contain personal information,
|
||||
as reports may contain detailed logs leading up to the crash.
|
||||
You can preview the log if you'd like using `qs log <path-to-log> -r '*=true'`.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -39,9 +39,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
|
||||
- `pkg-config` (build-time)
|
||||
- `cli11` (static library)
|
||||
|
||||
Quickshell relies on private Qt APIs and **MUST** be rebuilt against the each Qt release
|
||||
or crashes via ABI mismatches will occur.
|
||||
|
||||
Build time dependencies and static libraries don't have to exist at runtime,
|
||||
however build time dependencies must be compiled for the architecture of
|
||||
the builder, while static libraries must be compiled for the architecture
|
||||
@@ -100,6 +97,13 @@ To disable: `-DSOCKETS=OFF`
|
||||
### Wayland
|
||||
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
|
||||
|
||||
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
|
||||
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
|
||||
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
|
||||
at runtime.
|
||||
|
||||
Currently supported Qt versions: `6.6`, `6.7`.
|
||||
|
||||
To disable: `-DWAYLAND=OFF`
|
||||
|
||||
Dependencies:
|
||||
|
||||
+6
-2
@@ -1,7 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(quickshell VERSION "0.3.1" LANGUAGES CXX C)
|
||||
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
|
||||
|
||||
set(UNRELEASED_FEATURES)
|
||||
set(UNRELEASED_FEATURES
|
||||
"network.2"
|
||||
"colorquant-imagerect"
|
||||
"window-parent"
|
||||
)
|
||||
|
||||
set(QT_MIN_VERSION "6.6.0")
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
@@ -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,7 +44,6 @@ 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
|
||||
@@ -74,11 +73,6 @@ 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,23 +0,0 @@
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed `ScreencopyView` not displaying when only lock surfaces are shown.
|
||||
- Fixed `WlSessionLockSurface.visible` crashing if accessed before backing surface creation.
|
||||
- Fixed `MprisPlayer` returning `rate` for `minRate` and `maxRate`.
|
||||
- Fixed missing/wrong change signals on various properties.
|
||||
- Fixed session lock crashes on sleep, wake, DPMS, and unlocking.
|
||||
- `QsWindow.updatesEnabled` makes sure windows are redrawn when set to true.
|
||||
- Fixed potential crashes from usage of `WindowsetProjection.screens` during monitor unplug.
|
||||
- Fixed crashes from accessing freed objects laundered through a `ScriptModel`.
|
||||
- Fixed crashes when a wifi network disappear.
|
||||
- Fixed unhandled notifications sending `NotificationClosed` out of order.
|
||||
- Fixed `qs kill` not waiting for the process to exit.
|
||||
- Fixed IPC calls from children of a crashed and relaunched process crashing.
|
||||
- Fixed `JsonAdapter` crashing when deserializing new objects into an array.
|
||||
- Fixed `Toplevel.unsetRectangle` crashing when calle.
|
||||
- Fixed intermittent `FileView` crashes when updating watched files.
|
||||
- Fixed `ColorQuantizer` deletion crashing if an operation was live.
|
||||
- Fixed crashes when failing to create a `ScreencopyView`.
|
||||
- Fixed `PwNodePeakMonitor` crashing when sampling a stream with mismatched channels from the node.
|
||||
- Fixed unsetting `PopupAnchor.item` causing a crash.
|
||||
- Fixed hiding the last `PanelWindow` on screen causing a crash under X11.
|
||||
- Fixed crashes when `ScriptModel.values` is set while processing previously set values.
|
||||
+1
-1
@@ -56,7 +56,7 @@
|
||||
|
||||
unwrapped = stdenv.mkDerivation {
|
||||
pname = "quickshell${lib.optionalString debug "-debug"}";
|
||||
version = "0.3.1";
|
||||
version = "0.2.1";
|
||||
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
|
||||
|
||||
dontWrapQtApps = true; # see wrappers
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
(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
|
||||
@@ -43,6 +43,7 @@ qt_add_library(quickshell-core STATIC
|
||||
toolsupport.cpp
|
||||
streamreader.cpp
|
||||
debuginfo.cpp
|
||||
hook.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-core
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "colorquantizer.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qatomic.h>
|
||||
#include <qcolor.h>
|
||||
@@ -27,12 +26,12 @@ QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg
|
||||
}
|
||||
|
||||
ColorQuantizerOperation::ColorQuantizerOperation(
|
||||
QUrl source,
|
||||
QUrl* source,
|
||||
qreal depth,
|
||||
QRect imageRect,
|
||||
qreal rescaleSize
|
||||
)
|
||||
: source(std::move(source))
|
||||
: source(source)
|
||||
, maxDepth(depth)
|
||||
, imageRect(imageRect)
|
||||
, rescaleSize(rescaleSize) {
|
||||
@@ -40,11 +39,11 @@ ColorQuantizerOperation::ColorQuantizerOperation(
|
||||
}
|
||||
|
||||
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
||||
if (shouldCancel.loadAcquire() || this->source.isEmpty()) return;
|
||||
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
|
||||
|
||||
this->colors.clear();
|
||||
|
||||
auto image = QImage(this->source.toLocalFile());
|
||||
auto image = QImage(this->source->toLocalFile());
|
||||
|
||||
if (this->imageRect.isValid()) {
|
||||
image = image.copy(this->imageRect);
|
||||
@@ -62,7 +61,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source.toString();
|
||||
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -192,8 +191,6 @@ void ColorQuantizerOperation::run() {
|
||||
|
||||
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
||||
|
||||
ColorQuantizer::~ColorQuantizer() { this->cancelAsync(); }
|
||||
|
||||
void ColorQuantizer::componentComplete() {
|
||||
this->componentCompleted = true;
|
||||
if (!this->mSource.isEmpty()) this->quantizeAsync();
|
||||
@@ -249,7 +246,7 @@ void ColorQuantizer::quantizeAsync() {
|
||||
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||
|
||||
this->liveOperation = new ColorQuantizerOperation(
|
||||
this->mSource,
|
||||
&this->mSource,
|
||||
this->mDepth,
|
||||
this->mImageRect,
|
||||
this->mRescaleSize
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qrect.h>
|
||||
#include <qrunnable.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qurl.h>
|
||||
@@ -18,7 +17,7 @@ class ColorQuantizerOperation
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
|
||||
explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize);
|
||||
|
||||
void run() override;
|
||||
void tryCancel();
|
||||
@@ -44,7 +43,7 @@ private:
|
||||
|
||||
QAtomicInteger<bool> shouldCancel = false;
|
||||
QList<QColor> colors;
|
||||
QUrl source;
|
||||
QUrl* source;
|
||||
qreal maxDepth;
|
||||
QRect imageRect;
|
||||
qreal rescaleSize;
|
||||
@@ -90,13 +89,11 @@ 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
|
||||
/// > recommended to rescale, otherwise the quantization process will take much longer.
|
||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
||||
|
||||
public:
|
||||
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
|
||||
~ColorQuantizer() override;
|
||||
Q_DISABLE_COPY_MOVE(ColorQuantizer);
|
||||
|
||||
void componentComplete() override;
|
||||
void classBegin() override {}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#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"
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#include "generation.hpp"
|
||||
|
||||
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
|
||||
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
|
||||
class PixmapCacheIconEngine: public QIconEngine {
|
||||
void paint(
|
||||
QPainter* /*unused*/,
|
||||
|
||||
+4
-20
@@ -257,21 +257,9 @@ 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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
} else if (instance->lastCategoryFilter) {
|
||||
instance->lastCategoryFilter(category);
|
||||
filter = CategoryFilter(category);
|
||||
}
|
||||
|
||||
for (const auto& rule: *instance->rules) {
|
||||
@@ -304,10 +292,6 @@ void LogManager::init(
|
||||
const QString& rules,
|
||||
const QString& prefix
|
||||
) {
|
||||
static bool alreadyInitialized = false;
|
||||
if (alreadyInitialized) return;
|
||||
alreadyInitialized = true;
|
||||
|
||||
auto* instance = LogManager::instance();
|
||||
instance->colorLogs = color;
|
||||
instance->timestampLogs = timestamp;
|
||||
@@ -546,7 +530,7 @@ void ThreadLogging::initFs() {
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
|
||||
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
|
||||
}
|
||||
|
||||
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
|
||||
|
||||
@@ -138,10 +138,6 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
+8
-11
@@ -1,7 +1,6 @@
|
||||
#include "paths.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <fcntl.h>
|
||||
@@ -10,6 +9,7 @@
|
||||
#include <qdir.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qpair.h>
|
||||
#include <qstandardpaths.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtversionchecks.h>
|
||||
@@ -412,11 +412,10 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
|
||||
return true;
|
||||
}
|
||||
|
||||
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
QPair<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);
|
||||
|
||||
@@ -428,22 +427,20 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
|
||||
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
|
||||
<< info.pid << ") at " << path;
|
||||
|
||||
if (info.pid == -1) {
|
||||
deadInstances.push_back(info);
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
if (info.pid == -1) {
|
||||
deadInstances.push_back(info);
|
||||
} else {
|
||||
liveInstances.push_back(info);
|
||||
}
|
||||
} else {
|
||||
qCDebug(logPaths) << "Skipped potential instance at" << path;
|
||||
}
|
||||
}
|
||||
|
||||
return {liveInstances, mismatchedInstances, deadInstances};
|
||||
return qMakePair(liveInstances, deadInstances);
|
||||
}
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
#pragma once
|
||||
#include <tuple>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatetime.h>
|
||||
#include <qdir.h>
|
||||
#include <qpair.h>
|
||||
@@ -32,7 +29,7 @@ public:
|
||||
static QString ipcPath(const QString& id);
|
||||
static bool
|
||||
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
|
||||
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
|
||||
collectInstances(const QString& path, const QString& display);
|
||||
|
||||
QDir* baseRunDir();
|
||||
|
||||
@@ -93,8 +93,6 @@ void PopupAnchor::setItem(QQuickItem* item) {
|
||||
QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
|
||||
QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
|
||||
}
|
||||
|
||||
emit this->itemChanged();
|
||||
}
|
||||
|
||||
void PopupAnchor::onWindowDestroyed() {
|
||||
@@ -111,7 +109,7 @@ void PopupAnchor::onItemDestroyed() {
|
||||
}
|
||||
|
||||
void PopupAnchor::onItemWindowChanged() {
|
||||
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem ? this->mItem->window() : nullptr)) {
|
||||
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
|
||||
this->setWindowInternal(window->proxy());
|
||||
} else {
|
||||
this->setWindowInternal(nullptr);
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
#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>
|
||||
|
||||
@@ -345,12 +343,6 @@ 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);
|
||||
|
||||
+1
-12
@@ -244,7 +244,7 @@ public:
|
||||
/// it is assumed that all unreleased features are present. The unreleased feature list
|
||||
/// may be omitted.
|
||||
///
|
||||
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
|
||||
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
|
||||
/// > has the same function available.
|
||||
/// >
|
||||
/// > ```qml
|
||||
@@ -254,17 +254,6 @@ 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; }
|
||||
|
||||
@@ -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, then any stored copies of its ShellMonitor will
|
||||
/// > [!WARNING] If the monitor is disconnected than 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
@@ -242,7 +242,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
|
||||
}
|
||||
|
||||
QRegion PendingRegion::applyTo(const QRect& rect) const {
|
||||
// if left as the default, don't combine it with the whole rect area, leave it as is.
|
||||
// if left as the default, dont combine it with the whole rect area, leave it as is.
|
||||
if (this->mIntersection == Intersection::Combine) {
|
||||
return this->build();
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -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 that's a separate reload scope.
|
||||
// if not then don't check its children as thats a seperate reload scope.
|
||||
} else {
|
||||
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
|
||||
if (reloadable != nullptr) return reloadable;
|
||||
|
||||
+1
-1
@@ -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 corresponds to
|
||||
/// enough to help the reloader figure out which object in the old revision corrosponds 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
@@ -89,7 +89,7 @@ void QmlScanner::scanDir(const QDir& dir) {
|
||||
QString qmldir;
|
||||
auto stream = QTextStream(&qmldir);
|
||||
|
||||
// can't derive a module name if not in shell path
|
||||
// cant 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 don't care about "import qs" as we always load the root folder
|
||||
// we dont 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; // can't unmask if a nested if passes
|
||||
mask = sourceMasked || mask; // cant unmask if a nested if passes
|
||||
ifScopes.append(mask);
|
||||
if (mask) isOverridden = true;
|
||||
sourceMasked = mask;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtversion.h>
|
||||
#include <qversionnumber.h>
|
||||
|
||||
#include "build.hpp"
|
||||
|
||||
@@ -22,12 +20,6 @@ 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());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ 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);
|
||||
|
||||
+28
-111
@@ -4,95 +4,39 @@
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qjsvalueiterator.h>
|
||||
#include <qlist.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtversionchecks.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace {
|
||||
struct RecursionPair {
|
||||
const QJSValue* a;
|
||||
const QJSValue* b;
|
||||
const RecursionPair* previous;
|
||||
};
|
||||
|
||||
bool qjsValueStructuralEq(
|
||||
const QJSValue& a,
|
||||
const QJSValue& b,
|
||||
const RecursionPair* activePair = nullptr
|
||||
) {
|
||||
if (a.strictlyEquals(b)) return true;
|
||||
if (!a.isObject() || !b.isObject() || a.isArray() != b.isArray()) return false;
|
||||
|
||||
for (const auto* pair = activePair; pair != nullptr; pair = pair->previous) {
|
||||
if (a.strictlyEquals(*pair->a) || b.strictlyEquals(*pair->b)) return false;
|
||||
}
|
||||
|
||||
const auto nextActivePair = RecursionPair {.a = &a, .b = &b, .previous = activePair};
|
||||
|
||||
auto aIter = QJSValueIterator(a);
|
||||
auto bIter = QJSValueIterator(b);
|
||||
|
||||
while (aIter.hasNext()) {
|
||||
if (!bIter.hasNext()) return false;
|
||||
|
||||
aIter.next();
|
||||
bIter.next();
|
||||
|
||||
const auto aName = aIter.name();
|
||||
const auto bName = bIter.name();
|
||||
const auto aValue = aIter.value();
|
||||
|
||||
if (aName == bName) {
|
||||
const auto bValue = bIter.value();
|
||||
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
|
||||
} else {
|
||||
if (!b.hasOwnProperty(aName)) return false;
|
||||
|
||||
const auto bValue = b.property(aName);
|
||||
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return !bIter.hasNext();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
auto anyChanges = false;
|
||||
|
||||
this->isModifying = true;
|
||||
void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
|
||||
this->hasActiveIterators = true;
|
||||
this->mValues.reserve(newValues.size());
|
||||
|
||||
auto iter = this->mValues.begin();
|
||||
auto newIter = newValues.begin();
|
||||
|
||||
auto comparisonValue = [this](const QJSValue& value) {
|
||||
if (value.hasProperty(this->cmpKey)) return value.property(this->cmpKey);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
auto valueCmp = [&, this](const QJSValue& a, const QJSValue& b) {
|
||||
if (a.strictlyEquals(b)) return true;
|
||||
|
||||
const auto aValue = this->cmpKey.isEmpty() ? a : comparisonValue(a);
|
||||
const auto bValue = this->cmpKey.isEmpty() ? b : comparisonValue(b);
|
||||
|
||||
if (this->mComparisonMode == ObjectComparison::Identity) {
|
||||
return aValue.strictlyEquals(bValue);
|
||||
} else {
|
||||
return qjsValueStructuralEq(aValue, bValue);
|
||||
// TODO: cache this
|
||||
auto getCmpKey = [this](const QVariant& v) {
|
||||
if (v.canConvert<QVariantMap>()) {
|
||||
auto vMap = v.value<QVariantMap>();
|
||||
if (vMap.contains(this->cmpKey)) {
|
||||
return vMap.value(this->cmpKey);
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
};
|
||||
|
||||
auto eqPredicate = [&](const QJSValue& b) {
|
||||
return [&](const QJSValue& a) { return valueCmp(a, b); };
|
||||
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
|
||||
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
|
||||
else return a == b;
|
||||
};
|
||||
|
||||
auto eqPredicate = [&](const QVariant& b) {
|
||||
return [&](const QVariant& a) { return variantCmp(a, b); };
|
||||
};
|
||||
|
||||
while (true) {
|
||||
@@ -105,7 +49,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
this->beginRemoveRows(QModelIndex(), startIndex, endIndex);
|
||||
this->mValues.erase(iter, this->mValues.end());
|
||||
this->endRemoveRows();
|
||||
anyChanges = true;
|
||||
|
||||
break;
|
||||
} else if (iter == this->mValues.end()) {
|
||||
@@ -116,10 +59,9 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
this->beginInsertRows(QModelIndex(), startIndex, endIndex);
|
||||
this->mValues.append(newValues.sliced(startIndex));
|
||||
this->endInsertRows();
|
||||
anyChanges = true;
|
||||
|
||||
break;
|
||||
} else if (!valueCmp(*newIter, *iter)) {
|
||||
} else if (!variantCmp(*newIter, *iter)) {
|
||||
auto oldIter = std::find_if(iter, this->mValues.end(), eqPredicate(*newIter));
|
||||
|
||||
if (oldIter != this->mValues.end()) {
|
||||
@@ -139,7 +81,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
this->beginRemoveRows(QModelIndex(), startIndex, index - 1);
|
||||
iter = this->mValues.erase(startIter, iter);
|
||||
this->endRemoveRows();
|
||||
anyChanges = true;
|
||||
} else {
|
||||
// Advance iters to capture a whole move sequence as a single operation if possible.
|
||||
auto oldStartIter = oldIter;
|
||||
@@ -147,7 +88,7 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
++oldIter;
|
||||
++newIter;
|
||||
} while (oldIter != this->mValues.end() && newIter != newValues.end()
|
||||
&& valueCmp(*oldIter, *newIter));
|
||||
&& variantCmp(*oldIter, *newIter));
|
||||
|
||||
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
||||
auto oldStartIndex =
|
||||
@@ -164,7 +105,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
|
||||
iter = this->mValues.begin() + (index + len);
|
||||
this->endMoveRows();
|
||||
anyChanges = true;
|
||||
}
|
||||
} else {
|
||||
auto startNewIter = newIter;
|
||||
@@ -190,9 +130,8 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
std::move_backward(iter, this->mValues.end() - len, this->mValues.end());
|
||||
iter = std::copy(startNewIter, newIter, iter);
|
||||
this->endInsertRows();
|
||||
anyChanges = true;
|
||||
}
|
||||
} else if (!newIter->strictlyEquals(*iter)) {
|
||||
} else if (*newIter != *iter) {
|
||||
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
|
||||
auto index = first;
|
||||
|
||||
@@ -201,42 +140,26 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
|
||||
++iter;
|
||||
++newIter;
|
||||
++index;
|
||||
} while (iter != this->mValues.end() && newIter != newValues.end()
|
||||
&& !newIter->strictlyEquals(*iter));
|
||||
} while (iter != this->mValues.end() && newIter != newValues.end() && *newIter != *iter);
|
||||
|
||||
this->dataChanged(
|
||||
this->index(first, 0, QModelIndex()),
|
||||
this->index(index - 1, 0, QModelIndex()),
|
||||
{Qt::UserRole}
|
||||
);
|
||||
|
||||
anyChanges = true;
|
||||
} else {
|
||||
++iter;
|
||||
++newIter;
|
||||
}
|
||||
}
|
||||
|
||||
this->isModifying = false;
|
||||
|
||||
if (this->stagedValues.has_value()) {
|
||||
auto values = *this->stagedValues;
|
||||
this->stagedValues.reset();
|
||||
anyChanges |= this->updateValuesUnique(values);
|
||||
}
|
||||
|
||||
return anyChanges;
|
||||
this->hasActiveIterators = false;
|
||||
}
|
||||
|
||||
void ScriptModel::setValues(const QList<QJSValue>& newValues) {
|
||||
// Re-entrant modification waits for the original modification to complete to avoid mishandled duplicates.
|
||||
if (this->isModifying) {
|
||||
this->stagedValues = newValues;
|
||||
return;
|
||||
}
|
||||
|
||||
auto changed = this->updateValuesUnique(newValues);
|
||||
if (changed) emit this->valuesChanged();
|
||||
void ScriptModel::setValues(const QVariantList& newValues) {
|
||||
if (newValues == this->mValues) return;
|
||||
this->updateValuesUnique(newValues);
|
||||
emit this->valuesChanged();
|
||||
}
|
||||
|
||||
void ScriptModel::setObjectProp(const QString& objectProp) {
|
||||
@@ -246,12 +169,6 @@ void ScriptModel::setObjectProp(const QString& objectProp) {
|
||||
emit this->objectPropChanged();
|
||||
}
|
||||
|
||||
void ScriptModel::setComparisonMode(ObjectComparison::Enum comparisonMode) {
|
||||
if (comparisonMode == this->mComparisonMode) return;
|
||||
this->mComparisonMode = comparisonMode;
|
||||
emit this->comparisonModeChanged();
|
||||
}
|
||||
|
||||
qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent != QModelIndex()) return 0;
|
||||
return static_cast<qint32>(this->mValues.length());
|
||||
@@ -259,7 +176,7 @@ qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
|
||||
|
||||
QVariant ScriptModel::data(const QModelIndex& index, qint32 role) const {
|
||||
if (role != Qt::UserRole) return QVariant();
|
||||
return this->mValues.at(index.row()).toVariant(QJSValue::RetainJSObjects);
|
||||
return this->mValues.at(index.row());
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ScriptModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; }
|
||||
@@ -1,31 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
///! @@ScriptModel value comparison mode.
|
||||
namespace ObjectComparison { // NOLINT
|
||||
Q_NAMESPACE;
|
||||
QML_ELEMENT;
|
||||
|
||||
enum Enum : quint8 {
|
||||
/// Compare values using strict JavaScript equality.
|
||||
Identity = 0,
|
||||
/// Compare records and arrays structurally.
|
||||
Structure = 1,
|
||||
};
|
||||
Q_ENUM_NS(Enum);
|
||||
|
||||
} // namespace ObjectComparison
|
||||
|
||||
///! QML model reflecting a javascript expression
|
||||
/// ScriptModel is a QML [Data Model] that generates model operations based on changes
|
||||
@@ -61,7 +40,6 @@ Q_ENUM_NS(Enum);
|
||||
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
|
||||
class ScriptModel: public QAbstractListModel {
|
||||
Q_OBJECT;
|
||||
// clang-format off
|
||||
/// The list of values to reflect in the model.
|
||||
/// > [!WARNING] ScriptModel currently only works with lists of *unique* values.
|
||||
/// > There must not be any duplicates in the given list, or behavior of the model is undefined.
|
||||
@@ -88,8 +66,7 @@ class ScriptModel: public QAbstractListModel {
|
||||
/// > values: [...DesktopEntries.applications.values].sort(...)
|
||||
/// > }
|
||||
/// > ```
|
||||
// TODO: use qjsobject for gc/free checks?
|
||||
Q_PROPERTY(QList<QJSValue> values READ values WRITE setValues NOTIFY valuesChanged);
|
||||
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
|
||||
/// The property that javascript objects passed into the model will be compared with.
|
||||
///
|
||||
/// For example, if `objectProp` is `"myprop"` then `{ myprop: "a", other: "y" }` and
|
||||
@@ -97,30 +74,21 @@ class ScriptModel: public QAbstractListModel {
|
||||
///
|
||||
/// Defaults to `""`, meaning no key.
|
||||
Q_PROPERTY(QString objectProp READ objectProp WRITE setObjectProp NOTIFY objectPropChanged);
|
||||
/// How values should be compared. Defaults to `ObjectComparison.Structure`.
|
||||
///
|
||||
/// Identity based comparison is faster if usable for a given model, and often
|
||||
/// achievable with @@objectProp.
|
||||
Q_PROPERTY(ObjectComparison::Enum comparisonMode READ comparisonMode WRITE setComparisonMode NOTIFY comparisonModeChanged);
|
||||
// clang-format on
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
[[nodiscard]] QList<QJSValue> values() const {
|
||||
[[nodiscard]] QVariantList values() const {
|
||||
auto values = this->mValues;
|
||||
// If not detached, the QML engine will invalidate iterators in updateValuesUnique.
|
||||
if (this->isModifying) values.detach();
|
||||
if (this->hasActiveIterators) values.detach();
|
||||
return values;
|
||||
}
|
||||
|
||||
void setValues(const QList<QJSValue>& newValues);
|
||||
void setValues(const QVariantList& newValues);
|
||||
|
||||
[[nodiscard]] QString objectProp() const { return this->cmpKey; }
|
||||
void setObjectProp(const QString& objectProp);
|
||||
|
||||
[[nodiscard]] ObjectComparison::Enum comparisonMode() const { return this->mComparisonMode; }
|
||||
void setComparisonMode(ObjectComparison::Enum comparisonMode);
|
||||
|
||||
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
@@ -128,14 +96,11 @@ public:
|
||||
signals:
|
||||
void valuesChanged();
|
||||
void objectPropChanged();
|
||||
void comparisonModeChanged();
|
||||
|
||||
private:
|
||||
QList<QJSValue> mValues;
|
||||
QVariantList mValues;
|
||||
QString cmpKey;
|
||||
ObjectComparison::Enum mComparisonMode = ObjectComparison::Structure;
|
||||
bool isModifying = false;
|
||||
std::optional<QList<QJSValue>> stagedValues;
|
||||
bool hasActiveIterators = false;
|
||||
|
||||
bool updateValuesUnique(const QList<QJSValue>& newValues);
|
||||
void updateValuesUnique(const QVariantList& newValues);
|
||||
};
|
||||
@@ -8,4 +8,3 @@ 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)
|
||||
@@ -1,47 +0,0 @@
|
||||
#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);
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class TestObjectModel: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
private slots:
|
||||
static void diffUpdateInsertRemove();
|
||||
static void diffUpdateReorder();
|
||||
};
|
||||
+49
-151
@@ -4,11 +4,10 @@
|
||||
#include <qabstractitemmodeltester.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qjsengine.h>
|
||||
#include <qjsvalue.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qobject.h>
|
||||
#include <qsignalspy.h>
|
||||
#include <qstring.h>
|
||||
#include <qtest.h>
|
||||
#include <qtestcase.h>
|
||||
@@ -16,6 +15,8 @@
|
||||
|
||||
#include "../scriptmodel.hpp"
|
||||
|
||||
using OpList = QList<ModelOperation>;
|
||||
|
||||
bool ModelOperation::operator==(const ModelOperation& other) const {
|
||||
return other.operation == this->operation && other.index == this->index
|
||||
&& other.length == this->length && other.destIndex == this->destIndex;
|
||||
@@ -43,44 +44,15 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
|
||||
return debug;
|
||||
}
|
||||
|
||||
ChangeObserver::ChangeObserver(QAbstractItemModel* model) {
|
||||
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ChangeObserver::onInsert);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ChangeObserver::onRemove);
|
||||
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ChangeObserver::onMove);
|
||||
}
|
||||
|
||||
void ChangeObserver::onInsert(const QModelIndex& parent, int first, int last) {
|
||||
QCOMPARE(parent, QModelIndex());
|
||||
this->mOperations.emplaceBack(ModelOperation::Insert, first, last - first + 1);
|
||||
}
|
||||
|
||||
void ChangeObserver::onRemove(const QModelIndex& parent, int first, int last) {
|
||||
QCOMPARE(parent, QModelIndex());
|
||||
this->mOperations.emplaceBack(ModelOperation::Remove, first, last - first + 1);
|
||||
}
|
||||
|
||||
void ChangeObserver::onMove(
|
||||
const QModelIndex& sourceParent,
|
||||
int sourceStart,
|
||||
int sourceEnd,
|
||||
const QModelIndex& destParent,
|
||||
int destStart
|
||||
) {
|
||||
QCOMPARE(sourceParent, QModelIndex());
|
||||
QCOMPARE(destParent, QModelIndex());
|
||||
this->mOperations
|
||||
.emplaceBack(ModelOperation::Move, sourceStart, sourceEnd - sourceStart + 1, destStart);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
|
||||
QString str;
|
||||
QDebug& operator<<(QDebug& debug, const QVariantList& list) {
|
||||
auto str = QString();
|
||||
|
||||
for (const auto& var: list) {
|
||||
if (var.isString()) {
|
||||
str += var.toString();
|
||||
if (var.canConvert<QChar>()) {
|
||||
str += var.value<QChar>();
|
||||
} else {
|
||||
qFatal() << "QJSValueList debug overridden in test";
|
||||
qFatal() << "QVariantList debug overridden in test";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,18 +60,6 @@ QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
|
||||
return debug;
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool qjsValueListsEqual(const QJSValueList& a, const QJSValueList& b) {
|
||||
if (a.length() != b.length()) return false;
|
||||
|
||||
for (auto i = 0; i != a.length(); i++) {
|
||||
if (!a.at(i).strictlyEquals(b.at(i))) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void TestScriptModel::unique_data() {
|
||||
QTest::addColumn<QString>("oldstr");
|
||||
QTest::addColumn<QString>("newstr");
|
||||
@@ -130,7 +90,7 @@ void TestScriptModel::unique_data() {
|
||||
QTest::addRow("move_overlapping")
|
||||
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
|
||||
|
||||
// Ensure iterators aren't skipping anything at the end of operations by performing
|
||||
// Ensure iterators arent skipping anything at the end of operations by performing
|
||||
// multiple back to back.
|
||||
|
||||
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
|
||||
@@ -157,127 +117,65 @@ void TestScriptModel::unique() {
|
||||
QFETCH(const QString, newstr);
|
||||
QFETCH(const OpList, operations);
|
||||
|
||||
auto strToJsValueList = [](const QString& str) -> QJSValueList {
|
||||
QJSValueList list;
|
||||
auto strToVariantList = [](const QString& str) -> QVariantList {
|
||||
QVariantList list;
|
||||
|
||||
for (auto c: str) {
|
||||
list.emplace_back(QString(c));
|
||||
list.emplace_back(c);
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
auto oldList = strToJsValueList(oldstr);
|
||||
auto newList = strToJsValueList(newstr);
|
||||
auto oldlist = strToVariantList(oldstr);
|
||||
auto newlist = strToVariantList(newstr);
|
||||
|
||||
ScriptModel model;
|
||||
auto model = ScriptModel();
|
||||
auto modelTester = QAbstractItemModelTester(&model);
|
||||
auto observer = ChangeObserver(&model);
|
||||
|
||||
model.setValues(oldList);
|
||||
QVERIFY(qjsValueListsEqual(model.values(), oldList));
|
||||
QCOMPARE_EQ(
|
||||
observer.operations(),
|
||||
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
|
||||
);
|
||||
OpList actualOperations;
|
||||
|
||||
observer.clear();
|
||||
|
||||
model.setValues(newList);
|
||||
QVERIFY(qjsValueListsEqual(model.values(), newList));
|
||||
QCOMPARE_EQ(observer.operations(), operations);
|
||||
}
|
||||
|
||||
void TestScriptModel::structuralEquality() {
|
||||
ScriptModel model;
|
||||
QJSEngine engine;
|
||||
|
||||
auto object = engine.newObject();
|
||||
object.setProperty("foo", "bar");
|
||||
object.setProperty("bar", "baz");
|
||||
object.setProperty("subobject", engine.newObject());
|
||||
|
||||
auto replacement = engine.newObject();
|
||||
replacement.setProperty("subobject", engine.newObject());
|
||||
replacement.setProperty("bar", "baz");
|
||||
replacement.setProperty("foo", "bar");
|
||||
|
||||
auto oldList = QList<QJSValue> {object};
|
||||
|
||||
auto newList = QList<QJSValue> {replacement};
|
||||
|
||||
auto modelTester = QAbstractItemModelTester(&model);
|
||||
auto observer = ChangeObserver(&model);
|
||||
|
||||
model.setValues(oldList);
|
||||
QVERIFY(qjsValueListsEqual(model.values(), oldList));
|
||||
QCOMPARE_EQ(
|
||||
observer.operations(),
|
||||
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
|
||||
);
|
||||
|
||||
observer.clear();
|
||||
|
||||
model.setValues(newList);
|
||||
QVERIFY(qjsValueListsEqual(model.values(), newList));
|
||||
QCOMPARE_EQ(observer.operations(), OpList());
|
||||
|
||||
auto structurallyEquals = [](const QJSValue& a, const QJSValue& b) {
|
||||
ScriptModel model;
|
||||
model.setValues({a});
|
||||
|
||||
auto observer = ChangeObserver(&model);
|
||||
model.setValues({b});
|
||||
return observer.operations().isEmpty();
|
||||
auto onInsert = [&](const QModelIndex& parent, int first, int last) {
|
||||
QCOMPARE(parent, QModelIndex());
|
||||
actualOperations << ModelOperation(ModelOperation::Insert, first, last - first + 1);
|
||||
};
|
||||
|
||||
auto different = engine.newObject();
|
||||
different.setProperty("foo", "bar");
|
||||
different.setProperty("bar", "baz");
|
||||
auto differentSubobject = engine.newObject();
|
||||
differentSubobject.setProperty("value", 1);
|
||||
different.setProperty("subobject", differentSubobject);
|
||||
QVERIFY(!structurallyEquals(object, different));
|
||||
|
||||
auto cycleA = engine.newObject();
|
||||
cycleA.setProperty("self", cycleA);
|
||||
auto cycleB = engine.newObject();
|
||||
cycleB.setProperty("self", cycleB);
|
||||
QVERIFY(!structurallyEquals(cycleA, cycleB));
|
||||
|
||||
const auto arrayA = engine.evaluate("[1, { value: 'x' }]");
|
||||
const auto arrayB = engine.evaluate("[1, { value: 'x' }]");
|
||||
QVERIFY(structurallyEquals(arrayA, arrayB));
|
||||
|
||||
auto sparseArrayA = engine.newArray(1);
|
||||
auto sparseArrayB = engine.newArray(2);
|
||||
QVERIFY(!structurallyEquals(sparseArrayA, sparseArrayB));
|
||||
}
|
||||
|
||||
void TestScriptModel::comparisonModes() {
|
||||
QJSEngine engine;
|
||||
auto makeValue = [&engine]() {
|
||||
auto value = engine.newObject();
|
||||
value.setProperty("nested", engine.newObject());
|
||||
return value;
|
||||
auto onRemove = [&](const QModelIndex& parent, int first, int last) {
|
||||
QCOMPARE(parent, QModelIndex());
|
||||
actualOperations << ModelOperation(ModelOperation::Remove, first, last - first + 1);
|
||||
};
|
||||
|
||||
ScriptModel model;
|
||||
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Structure);
|
||||
auto onMove = [&](const QModelIndex& sourceParent,
|
||||
int sourceStart,
|
||||
int sourceEnd,
|
||||
const QModelIndex& destParent,
|
||||
int destStart) {
|
||||
QCOMPARE(sourceParent, QModelIndex());
|
||||
QCOMPARE(destParent, QModelIndex());
|
||||
actualOperations << ModelOperation(
|
||||
ModelOperation::Move,
|
||||
sourceStart,
|
||||
sourceEnd - sourceStart + 1,
|
||||
destStart
|
||||
);
|
||||
};
|
||||
|
||||
model.setValues({makeValue()});
|
||||
model.setComparisonMode(ObjectComparison::Identity);
|
||||
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Identity);
|
||||
QObject::connect(&model, &QAbstractItemModel::rowsInserted, &model, onInsert);
|
||||
QObject::connect(&model, &QAbstractItemModel::rowsRemoved, &model, onRemove);
|
||||
QObject::connect(&model, &QAbstractItemModel::rowsMoved, &model, onMove);
|
||||
|
||||
auto observer = ChangeObserver(&model);
|
||||
model.setValues({makeValue()});
|
||||
QVERIFY(!observer.operations().isEmpty());
|
||||
model.setValues(oldlist);
|
||||
QCOMPARE_EQ(model.values(), oldlist);
|
||||
QCOMPARE_EQ(
|
||||
actualOperations,
|
||||
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldlist.length())}})
|
||||
);
|
||||
|
||||
model.setComparisonMode(ObjectComparison::Structure);
|
||||
actualOperations.clear();
|
||||
|
||||
observer.clear();
|
||||
model.setValues({makeValue()});
|
||||
QCOMPARE_EQ(observer.operations(), OpList());
|
||||
model.setValues(newlist);
|
||||
QCOMPARE_EQ(model.values(), newlist);
|
||||
QCOMPARE_EQ(actualOperations, operations);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TestScriptModel);
|
||||
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <qabstractitemmodel.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlist.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
@@ -28,29 +26,6 @@ struct ModelOperation {
|
||||
[[nodiscard]] bool operator==(const ModelOperation& other) const;
|
||||
};
|
||||
|
||||
using OpList = QList<ModelOperation>;
|
||||
|
||||
class ChangeObserver: public QObject {
|
||||
public:
|
||||
explicit ChangeObserver(QAbstractItemModel* model);
|
||||
|
||||
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
|
||||
void clear() { this->mOperations.clear(); }
|
||||
|
||||
private:
|
||||
void onInsert(const QModelIndex& parent, int first, int last);
|
||||
void onRemove(const QModelIndex& parent, int first, int last);
|
||||
void onMove(
|
||||
const QModelIndex& sourceParent,
|
||||
int sourceStart,
|
||||
int sourceEnd,
|
||||
const QModelIndex& destParent,
|
||||
int destStart
|
||||
);
|
||||
|
||||
OpList mOperations;
|
||||
};
|
||||
|
||||
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
|
||||
|
||||
class TestScriptModel: public QObject {
|
||||
@@ -59,6 +34,4 @@ class TestScriptModel: public QObject {
|
||||
private slots:
|
||||
static void unique_data(); // NOLINT
|
||||
static void unique();
|
||||
static void structuralEquality();
|
||||
static void comparisonModes();
|
||||
};
|
||||
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
|
||||
|
||||
{
|
||||
if (this->mInstances.contains(variant)) {
|
||||
continue; // we don't need to recreate this one
|
||||
continue; // we dont need to recreate this one
|
||||
}
|
||||
|
||||
auto variantMap = QVariantMap();
|
||||
|
||||
@@ -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 populated with a value
|
||||
/// The delegate should define a `modelData` property that will be popuplated with a value
|
||||
/// from the @@model.
|
||||
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
|
||||
/// The list of sets of properties to create instances with.
|
||||
|
||||
@@ -301,13 +301,6 @@ 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();
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure we don't miss any delimiters in the buffer if the delimiter changes
|
||||
// make sure we dont miss any delimiters in the buffer if the delimiter changes
|
||||
if (this->mSplitMarkerChanged) {
|
||||
this->mSplitMarkerChanged = false;
|
||||
this->parseBytes(buffer, buffer);
|
||||
|
||||
@@ -495,7 +495,6 @@ void FileView::updateWatchedFiles() {
|
||||
// If inotify events are sent to the watcher after deletion and deleteLater
|
||||
// isn't used, a use after free in the QML engine will occur.
|
||||
if (this->watcher) {
|
||||
QObject::disconnect(this->watcher, nullptr, this, nullptr);
|
||||
this->watcher->deleteLater();
|
||||
this->watcher = nullptr;
|
||||
}
|
||||
|
||||
+3
-3
@@ -29,9 +29,9 @@ class FileViewError: public QObject {
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// No error occurred.
|
||||
/// No error occured.
|
||||
Success = 0,
|
||||
/// An unknown error occurred. Check the logs for details.
|
||||
/// An unknown error occured. Check the logs for details.
|
||||
Unknown = 1,
|
||||
/// The file to read does not exist.
|
||||
FileNotFound = 2,
|
||||
@@ -204,7 +204,7 @@ class FileView: public QObject {
|
||||
///
|
||||
/// > [!NOTE] This works by creating another file with the desired content, and renaming
|
||||
/// > it over the existing file if successful.
|
||||
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY atomicWritesChanged BINDABLE bindableAtomicWrites);
|
||||
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableAtomicWrites);
|
||||
/// If true (default), read or write errors will be printed to the quickshell logs.
|
||||
/// If false, all known errors will not be printed.
|
||||
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
|
||||
|
||||
+154
-67
@@ -1,94 +1,181 @@
|
||||
#include "ipc.hpp"
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
bool IpcValueSlot::isSupported(const QMetaType& type) { return IpcValueSlot::typeName(type); }
|
||||
const VoidIpcType VoidIpcType::INSTANCE {};
|
||||
const StringIpcType StringIpcType::INSTANCE {};
|
||||
const IntIpcType IntIpcType::INSTANCE {};
|
||||
const BoolIpcType BoolIpcType::INSTANCE {};
|
||||
const DoubleIpcType DoubleIpcType::INSTANCE {};
|
||||
const ColorIpcType ColorIpcType::INSTANCE {};
|
||||
|
||||
const char* IpcValueSlot::typeName(const QMetaType& type) {
|
||||
switch (type.id()) {
|
||||
case QMetaType::Void: return "void";
|
||||
case QMetaType::QString: return "string";
|
||||
case QMetaType::Int: return "int";
|
||||
case QMetaType::Bool: return "bool";
|
||||
case QMetaType::Double: return "real";
|
||||
case QMetaType::QColor: return "color";
|
||||
default: return nullptr;
|
||||
void* IpcType::copyStorage(const void* data) const {
|
||||
auto* storage = this->createStorage();
|
||||
memcpy(storage, data, this->size());
|
||||
return storage;
|
||||
}
|
||||
|
||||
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
|
||||
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
|
||||
|
||||
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
|
||||
this->mType = other.mType;
|
||||
this->storage = other.storage;
|
||||
other.mType = nullptr;
|
||||
other.storage = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
|
||||
|
||||
const IpcType* IpcTypeSlot::type() const { return this->mType; }
|
||||
|
||||
void* IpcTypeSlot::get() {
|
||||
if (this->storage == nullptr) {
|
||||
this->storage = this->mType->createStorage();
|
||||
}
|
||||
|
||||
return this->storage;
|
||||
}
|
||||
|
||||
QGenericArgument IpcTypeSlot::asGenericArgument() {
|
||||
if (this->mType) {
|
||||
return QGenericArgument(this->mType->genericArgumentName(), this->get());
|
||||
} else {
|
||||
return QGenericArgument();
|
||||
}
|
||||
}
|
||||
|
||||
const char* IpcValueSlot::name() const {
|
||||
return this->mValue.isValid() ? IpcValueSlot::typeName(this->mValue.metaType()) : "void";
|
||||
}
|
||||
|
||||
bool IpcValueSlot::isVoid() const { return !this->mValue.isValid(); }
|
||||
|
||||
const QVariant& IpcValueSlot::value() const { return this->mValue; }
|
||||
|
||||
bool IpcValueSlot::setString(const QString& string) {
|
||||
switch (this->mValue.typeId()) {
|
||||
case QMetaType::QString: this->mValue = QVariant::fromValue(string); return true;
|
||||
case QMetaType::Int: {
|
||||
auto ok = false;
|
||||
auto value = string.toInt(&ok);
|
||||
if (ok) this->mValue = QVariant::fromValue(value);
|
||||
return ok;
|
||||
}
|
||||
case QMetaType::Bool: {
|
||||
if (string == "true" || string == "false") {
|
||||
this->mValue = QVariant::fromValue(string == "true");
|
||||
return true;
|
||||
}
|
||||
|
||||
auto ok = false;
|
||||
auto value = string.toInt(&ok);
|
||||
if (ok) this->mValue = QVariant::fromValue(value != 0);
|
||||
return ok;
|
||||
}
|
||||
case QMetaType::Double: {
|
||||
auto ok = false;
|
||||
auto value = string.toDouble(&ok);
|
||||
if (ok) this->mValue = QVariant::fromValue(value);
|
||||
return ok;
|
||||
}
|
||||
case QMetaType::QColor: {
|
||||
auto value = QColor::fromString(string);
|
||||
if (!value.isValid() && !string.startsWith('#')) value = QColor::fromString('#' % string);
|
||||
if (value.isValid()) this->mValue = QVariant::fromValue(value);
|
||||
return value.isValid();
|
||||
}
|
||||
default: return false;
|
||||
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
|
||||
if (this->mType) {
|
||||
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
|
||||
} else {
|
||||
return QGenericReturnArgument();
|
||||
}
|
||||
}
|
||||
|
||||
void IpcValueSlot::setValue(QVariant value) { this->mValue = std::move(value); }
|
||||
|
||||
QString IpcValueSlot::toString() const {
|
||||
switch (this->mValue.typeId()) {
|
||||
case QMetaType::UnknownType: return "";
|
||||
case QMetaType::QString: return this->mValue.value<QString>();
|
||||
case QMetaType::Int: return QString::number(this->mValue.value<int>());
|
||||
case QMetaType::Bool: return this->mValue.value<bool>() ? "true" : "false";
|
||||
case QMetaType::Double: return QString::number(this->mValue.value<double>());
|
||||
case QMetaType::QColor: return this->mValue.value<QColor>().name(QColor::HexArgb);
|
||||
default: return "";
|
||||
void IpcTypeSlot::replace(void* value) {
|
||||
if (this->storage != nullptr) {
|
||||
this->mType->destroyStorage(this->storage);
|
||||
}
|
||||
|
||||
this->storage = value;
|
||||
}
|
||||
|
||||
QGenericArgument IpcValueSlot::asGenericArgument() const {
|
||||
return QGenericArgument(this->mValue.metaType().name(), this->mValue.constData());
|
||||
void IpcTypeSlot::replace(const QVariant& value) {
|
||||
this->replace(this->mType->copyStorage(value.constData()));
|
||||
}
|
||||
|
||||
QGenericReturnArgument IpcValueSlot::asGenericReturnArgument() {
|
||||
if (!this->mValue.isValid()) return QGenericReturnArgument();
|
||||
return QGenericReturnArgument(this->mValue.metaType().name(), this->mValue.data());
|
||||
const char* VoidIpcType::name() const { return "void"; }
|
||||
const char* VoidIpcType::genericArgumentName() const { return "void"; }
|
||||
qsizetype VoidIpcType::size() const { return 0; }
|
||||
|
||||
// string
|
||||
const char* StringIpcType::name() const { return "string"; }
|
||||
const char* StringIpcType::genericArgumentName() const { return "QString"; }
|
||||
qsizetype StringIpcType::size() const { return sizeof(QString); }
|
||||
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
|
||||
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
|
||||
void* StringIpcType::createStorage() const { return new QString(); }
|
||||
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
|
||||
|
||||
// int
|
||||
const char* IntIpcType::name() const { return "int"; }
|
||||
const char* IntIpcType::genericArgumentName() const { return "int"; }
|
||||
qsizetype IntIpcType::size() const { return sizeof(int); }
|
||||
|
||||
void* IntIpcType::fromString(const QString& string) const {
|
||||
auto ok = false;
|
||||
auto v = string.toInt(&ok);
|
||||
|
||||
return ok ? new int(v) : nullptr;
|
||||
}
|
||||
|
||||
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
|
||||
|
||||
void* IntIpcType::createStorage() const { return new int(); }
|
||||
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
|
||||
|
||||
// bool
|
||||
const char* BoolIpcType::name() const { return "bool"; }
|
||||
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
|
||||
qsizetype BoolIpcType::size() const { return sizeof(bool); }
|
||||
|
||||
void* BoolIpcType::fromString(const QString& string) const {
|
||||
if (string == "true") return new bool(true);
|
||||
if (string == "false") return new bool(false);
|
||||
|
||||
auto isInt = false;
|
||||
auto iv = string.toInt(&isInt);
|
||||
|
||||
return isInt ? new bool(iv != 0) : nullptr;
|
||||
}
|
||||
|
||||
QString BoolIpcType::toString(void* slot) const {
|
||||
return *static_cast<bool*>(slot) ? "true" : "false";
|
||||
}
|
||||
|
||||
void* BoolIpcType::createStorage() const { return new bool(); }
|
||||
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
|
||||
|
||||
// double
|
||||
const char* DoubleIpcType::name() const { return "real"; }
|
||||
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
|
||||
qsizetype DoubleIpcType::size() const { return sizeof(double); }
|
||||
|
||||
void* DoubleIpcType::fromString(const QString& string) const {
|
||||
auto ok = false;
|
||||
auto v = string.toDouble(&ok);
|
||||
|
||||
return ok ? new double(v) : nullptr;
|
||||
}
|
||||
|
||||
QString DoubleIpcType::toString(void* slot) const {
|
||||
return QString::number(*static_cast<double*>(slot));
|
||||
}
|
||||
|
||||
void* DoubleIpcType::createStorage() const { return new double(); }
|
||||
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
|
||||
|
||||
// color
|
||||
const char* ColorIpcType::name() const { return "color"; }
|
||||
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
|
||||
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
|
||||
|
||||
void* ColorIpcType::fromString(const QString& string) const {
|
||||
auto color = QColor::fromString(string);
|
||||
|
||||
if (!color.isValid() && !string.startsWith('#')) {
|
||||
color = QColor::fromString('#' % string);
|
||||
}
|
||||
|
||||
return color.isValid() ? new QColor(color) : nullptr;
|
||||
}
|
||||
|
||||
QString ColorIpcType::toString(void* slot) const {
|
||||
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
|
||||
}
|
||||
|
||||
void* ColorIpcType::createStorage() const { return new bool(); }
|
||||
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
|
||||
|
||||
QString WireFunctionDefinition::toString() const {
|
||||
QString paramString;
|
||||
for (const auto& [name, type]: this->arguments) {
|
||||
|
||||
+110
-15
@@ -1,35 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
#include "../ipc/ipc.hpp"
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
class IpcValueSlot {
|
||||
class IpcTypeSlot;
|
||||
|
||||
class IpcType {
|
||||
public:
|
||||
IpcValueSlot() = default;
|
||||
explicit IpcValueSlot(QMetaType type): mValue(type) {}
|
||||
IpcType() = default;
|
||||
virtual ~IpcType() = default;
|
||||
IpcType(const IpcType&) = default;
|
||||
IpcType(IpcType&&) = default;
|
||||
IpcType& operator=(const IpcType&) = default;
|
||||
IpcType& operator=(IpcType&&) = default;
|
||||
|
||||
[[nodiscard]] static bool isSupported(const QMetaType& type);
|
||||
[[nodiscard]] static const char* typeName(const QMetaType& type);
|
||||
[[nodiscard]] const char* name() const;
|
||||
[[nodiscard]] bool isVoid() const;
|
||||
[[nodiscard]] const QVariant& value() const;
|
||||
[[nodiscard]] virtual const char* name() const = 0;
|
||||
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
|
||||
[[nodiscard]] virtual qsizetype size() const = 0;
|
||||
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
|
||||
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
|
||||
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
|
||||
virtual void destroyStorage(void* /*slot*/) const {}
|
||||
void* copyStorage(const void* data) const;
|
||||
|
||||
bool setString(const QString& string);
|
||||
void setValue(QVariant value);
|
||||
[[nodiscard]] QString toString() const;
|
||||
static const IpcType* ipcType(const QMetaType& metaType);
|
||||
};
|
||||
|
||||
[[nodiscard]] QGenericArgument asGenericArgument() const;
|
||||
class IpcTypeSlot {
|
||||
public:
|
||||
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
|
||||
~IpcTypeSlot();
|
||||
Q_DISABLE_COPY(IpcTypeSlot);
|
||||
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
|
||||
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
|
||||
|
||||
[[nodiscard]] const IpcType* type() const;
|
||||
[[nodiscard]] void* get();
|
||||
[[nodiscard]] QGenericArgument asGenericArgument();
|
||||
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
|
||||
|
||||
void replace(void* value);
|
||||
void replace(const QVariant& value);
|
||||
|
||||
private:
|
||||
QVariant mValue;
|
||||
const IpcType* mType = nullptr;
|
||||
void* storage = nullptr;
|
||||
};
|
||||
|
||||
class VoidIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
|
||||
static const VoidIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class StringIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const StringIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class IntIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const IntIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class BoolIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const BoolIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class DoubleIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const DoubleIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class ColorIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] qsizetype size() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const ColorIpcType INSTANCE;
|
||||
};
|
||||
|
||||
struct WireFunctionDefinition {
|
||||
|
||||
+3
-3
@@ -173,7 +173,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
|
||||
func->invoke(handler, storage);
|
||||
|
||||
resp << Completed {
|
||||
.isVoid = func->returnType.isVoid(),
|
||||
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
|
||||
.returnValue = storage.getReturnStr(),
|
||||
};
|
||||
} else {
|
||||
@@ -269,11 +269,11 @@ void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
|
||||
return;
|
||||
}
|
||||
|
||||
auto slot = IpcValueSlot(prop->type);
|
||||
auto slot = IpcTypeSlot(prop->type);
|
||||
prop->read(handler, slot);
|
||||
|
||||
resp << PropertyValue {
|
||||
.value = slot.toString(),
|
||||
.value = slot.type()->toString(slot.get()),
|
||||
};
|
||||
} else {
|
||||
conn->respond(StringCallResponse(NoCurrentGeneration()));
|
||||
|
||||
+39
-36
@@ -34,8 +34,9 @@ bool IpcFunction::resolve(QString& error) {
|
||||
|
||||
for (auto i = 0; i < this->method.parameterCount(); i++) {
|
||||
const auto& metaType = this->method.parameterMetaType(i);
|
||||
const auto* type = IpcType::ipcType(metaType);
|
||||
|
||||
if (!IpcValueSlot::isSupported(metaType)) {
|
||||
if (type == nullptr) {
|
||||
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
|
||||
.arg(i + 1)
|
||||
.arg(this->method.parameterNames().value(i))
|
||||
@@ -44,20 +45,23 @@ bool IpcFunction::resolve(QString& error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->argumentTypes.emplaceBack(metaType);
|
||||
this->argumentTypes.append(type);
|
||||
}
|
||||
|
||||
auto metaType = this->method.returnMetaType();
|
||||
const auto& metaType = this->method.returnMetaType();
|
||||
const auto* type = IpcType::ipcType(metaType);
|
||||
|
||||
// void and var get mixed by qml engine in return types
|
||||
if (metaType.id() == QMetaType::QVariant) metaType = QMetaType::fromType<void>();
|
||||
if (type == nullptr) {
|
||||
// void and var get mixed by qml engine in return types
|
||||
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
|
||||
|
||||
if (!IpcValueSlot::isSupported(metaType)) {
|
||||
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
|
||||
return false;
|
||||
if (type == nullptr) {
|
||||
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->returnType = IpcValueSlot(metaType);
|
||||
this->returnType = type;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -88,53 +92,52 @@ QString IpcFunction::toString() const {
|
||||
QString paramString;
|
||||
auto paramNames = this->method.parameterNames();
|
||||
for (auto i = 0; i < this->argumentTypes.length(); i++) {
|
||||
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i).name();
|
||||
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
|
||||
|
||||
if (i + 1 != this->argumentTypes.length()) {
|
||||
paramString += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType.name();
|
||||
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
|
||||
}
|
||||
|
||||
WireFunctionDefinition IpcFunction::wireDef() const {
|
||||
WireFunctionDefinition wire;
|
||||
wire.name = this->method.name();
|
||||
wire.returnType = this->returnType.name();
|
||||
wire.returnType = this->returnType->name();
|
||||
|
||||
auto paramNames = this->method.parameterNames();
|
||||
for (auto i = 0; i < this->argumentTypes.length(); i++) {
|
||||
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i).name());
|
||||
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
|
||||
}
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
bool IpcProperty::resolve(QString& error) {
|
||||
const auto& metaType = this->property.metaType();
|
||||
this->type = IpcType::ipcType(this->property.metaType());
|
||||
|
||||
if (!IpcValueSlot::isSupported(metaType)) {
|
||||
error = QString("Type %1 cannot be used across IPC.").arg(metaType.name());
|
||||
if (!this->type) {
|
||||
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
|
||||
return false;
|
||||
}
|
||||
|
||||
this->type = metaType;
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpcProperty::read(QObject* target, IpcValueSlot& slot) const {
|
||||
slot.setValue(this->property.read(target));
|
||||
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
|
||||
slot.replace(this->property.read(target));
|
||||
}
|
||||
|
||||
QString IpcProperty::toString() const {
|
||||
return QString("property ") % this->property.name() % ": " % IpcValueSlot::typeName(this->type);
|
||||
return QString("property ") % this->property.name() % ": " % this->type->name();
|
||||
}
|
||||
|
||||
WirePropertyDefinition IpcProperty::wireDef() const {
|
||||
WirePropertyDefinition wire;
|
||||
wire.name = this->property.name();
|
||||
wire.type = IpcValueSlot::typeName(this->type);
|
||||
wire.type = this->type->name();
|
||||
return wire;
|
||||
}
|
||||
|
||||
@@ -214,15 +217,21 @@ IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function
|
||||
}
|
||||
|
||||
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
|
||||
return this->argumentSlots.at(i).setString(value);
|
||||
auto& slot = this->argumentSlots.at(i);
|
||||
|
||||
auto* data = slot.type()->fromString(value);
|
||||
slot.replace(data);
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
QString IpcCallStorage::getReturnStr() { return this->returnSlot.toString(); }
|
||||
QString IpcCallStorage::getReturnStr() {
|
||||
return this->returnSlot.type()->toString(this->returnSlot.get());
|
||||
}
|
||||
|
||||
IpcHandler::~IpcHandler() {
|
||||
if (this->registeredState.enabled) {
|
||||
this->targetState.enabled = false;
|
||||
this->updateRegistration();
|
||||
this->updateRegistration(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,34 +310,28 @@ void IpcHandler::onSignalTriggered(const QString& signal, const QString& value)
|
||||
-> triggered(this->registeredState.target, signal, value);
|
||||
}
|
||||
|
||||
void IpcHandler::updateRegistration() {
|
||||
void IpcHandler::updateRegistration(bool destroying) {
|
||||
if (!this->complete) return;
|
||||
|
||||
IpcHandlerRegistry* registry = nullptr;
|
||||
if (this->registeredState.enabled) {
|
||||
registry = this->registry.get();
|
||||
} else {
|
||||
auto* generation = EngineGeneration::findObjectGeneration(this);
|
||||
auto* generation = EngineGeneration::findObjectGeneration(this);
|
||||
|
||||
if (!generation) {
|
||||
if (!generation) {
|
||||
if (!destroying) {
|
||||
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
|
||||
return;
|
||||
}
|
||||
|
||||
registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!registry) return;
|
||||
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
|
||||
if (this->registeredState.enabled) {
|
||||
registry->deregisterHandler(this);
|
||||
this->registry = nullptr;
|
||||
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
|
||||
}
|
||||
|
||||
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
|
||||
registry->registerHandler(this);
|
||||
this->registry = registry;
|
||||
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-16
@@ -11,7 +11,6 @@
|
||||
#include <qmetaobject.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qpointer.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
@@ -38,8 +37,8 @@ public:
|
||||
[[nodiscard]] WireFunctionDefinition wireDef() const;
|
||||
|
||||
QMetaMethod method;
|
||||
QVector<IpcValueSlot> argumentTypes;
|
||||
IpcValueSlot returnType;
|
||||
QVector<const IpcType*> argumentTypes;
|
||||
const IpcType* returnType = nullptr;
|
||||
};
|
||||
|
||||
class IpcCallStorage {
|
||||
@@ -50,8 +49,8 @@ public:
|
||||
[[nodiscard]] QString getReturnStr();
|
||||
|
||||
private:
|
||||
std::vector<IpcValueSlot> argumentSlots;
|
||||
IpcValueSlot returnSlot;
|
||||
std::vector<IpcTypeSlot> argumentSlots;
|
||||
IpcTypeSlot returnSlot;
|
||||
|
||||
friend class IpcFunction;
|
||||
};
|
||||
@@ -61,13 +60,13 @@ public:
|
||||
explicit IpcProperty(QMetaProperty property): property(property) {}
|
||||
|
||||
bool resolve(QString& error);
|
||||
void read(QObject* target, IpcValueSlot& slot) const;
|
||||
void read(QObject* target, IpcTypeSlot& slot) const;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
[[nodiscard]] WirePropertyDefinition wireDef() const;
|
||||
|
||||
QMetaProperty property;
|
||||
QMetaType type;
|
||||
const IpcType* type = nullptr;
|
||||
};
|
||||
|
||||
class IpcSignalListener: public QObject {
|
||||
@@ -186,7 +185,7 @@ class IpcHandlerRegistry;
|
||||
///
|
||||
/// function getRadius(): int { return rect.radius; }
|
||||
///
|
||||
/// signal radiusChanged(newRadius: int);
|
||||
/// signal radiusChanged(newRadius: int);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
@@ -255,7 +254,7 @@ public slots:
|
||||
void onSignalTriggered(const QString& signal, const QString& value) const;
|
||||
|
||||
private:
|
||||
void updateRegistration();
|
||||
void updateRegistration(bool destroying = false);
|
||||
|
||||
struct RegistrationState {
|
||||
explicit RegistrationState(bool enabled = false): enabled(enabled) {}
|
||||
@@ -272,16 +271,10 @@ private:
|
||||
QHash<QString, IpcProperty> propertyMap;
|
||||
QHash<QString, IpcSignal> signalMap;
|
||||
|
||||
QPointer<IpcHandlerRegistry> registry;
|
||||
|
||||
friend class IpcHandlerRegistry;
|
||||
};
|
||||
|
||||
class IpcHandlerRegistry
|
||||
: public QObject
|
||||
, public EngineGenerationExt {
|
||||
Q_OBJECT;
|
||||
|
||||
class IpcHandlerRegistry: public EngineGenerationExt {
|
||||
public:
|
||||
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
|
||||
|
||||
|
||||
@@ -225,15 +225,15 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
|
||||
const auto& jsonValue = array.at(i);
|
||||
if (jsonValue.isObject()) {
|
||||
if (isNew) {
|
||||
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
||||
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
|
||||
currentValue->setParent(this);
|
||||
this->createdObjects.push_back(currentValue);
|
||||
} else {
|
||||
currentValue = lp.at(&lp, i);
|
||||
if (this->oldCreatedObjects.removeOne(currentValue)) {
|
||||
this->createdObjects.push_back(currentValue);
|
||||
}
|
||||
} else {
|
||||
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
|
||||
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
|
||||
currentValue->setParent(this);
|
||||
this->createdObjects.push_back(currentValue);
|
||||
}
|
||||
|
||||
this->deserializeRec(
|
||||
|
||||
+2
-2
@@ -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 destroy all active connections and delete
|
||||
/// Setting this to false will destory 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 create a @@Socket.
|
||||
/// Connection handler component. Must creeate 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.)
|
||||
|
||||
+3
-11
@@ -105,19 +105,11 @@ IpcClient::IpcClient(const QString& path) {
|
||||
bool IpcClient::isConnected() const { return this->socket.isValid(); }
|
||||
|
||||
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
|
||||
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
|
||||
|
||||
void IpcClient::waitForDisconnected() {
|
||||
this->waitingForDisconnect = true;
|
||||
this->socket.waitForDisconnected();
|
||||
}
|
||||
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
|
||||
|
||||
void IpcClient::kill() {
|
||||
this->sendMessage(IpcCommand(IpcKillCommand()));
|
||||
this->waitForDisconnected();
|
||||
}
|
||||
|
||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) const {
|
||||
if (this->waitingForDisconnect && error == QLocalSocket::PeerClosedError) return;
|
||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
|
||||
qCCritical(logIpc) << "Socket Error" << error;
|
||||
}
|
||||
|
||||
|
||||
+2
-5
@@ -19,7 +19,7 @@
|
||||
|
||||
template <typename... Types>
|
||||
constexpr void assertSerializable() {
|
||||
// monostate being zero ensures transactional reads won't break
|
||||
// monostate being zero ensures transactional reads wont break
|
||||
static_assert(
|
||||
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
|
||||
"Serialization of variants without std::monostate at index 0 is disallowed."
|
||||
@@ -216,10 +216,7 @@ signals:
|
||||
void disconnected();
|
||||
|
||||
private slots:
|
||||
void onError(QLocalSocket::LocalSocketError error) const;
|
||||
|
||||
private:
|
||||
bool waitingForDisconnect = false;
|
||||
static void onError(QLocalSocket::LocalSocketError error);
|
||||
};
|
||||
|
||||
} // namespace qs::ipc
|
||||
+34
-72
@@ -12,6 +12,7 @@
|
||||
#include <qdebug.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
@@ -119,7 +120,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
qCCritical(logBare) << "Could not open manifest at path" << *cmd.config.manifest;
|
||||
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
@@ -164,12 +165,7 @@ void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
|
||||
});
|
||||
};
|
||||
|
||||
int selectInstance(
|
||||
CommandState& cmd,
|
||||
InstanceLockInfo* instance,
|
||||
bool deadFallback = false,
|
||||
bool logHelp = true
|
||||
) {
|
||||
int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallback = false) {
|
||||
auto* basePath = QsPaths::instance()->baseRunDir();
|
||||
if (!basePath) return -1;
|
||||
|
||||
@@ -178,22 +174,18 @@ int selectInstance(
|
||||
if (cmd.instance.pid != -1) {
|
||||
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
|
||||
if (!QsPaths::checkLock(path, instance, deadFallback)) {
|
||||
if (logHelp) qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
|
||||
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
|
||||
return -1;
|
||||
}
|
||||
} else if (!cmd.instance.id->isEmpty()) {
|
||||
path = basePath->filePath("by-pid");
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||
auto [liveInstances, 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);
|
||||
});
|
||||
@@ -201,43 +193,27 @@ int selectInstance(
|
||||
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
if (logHelp) {
|
||||
if (!mismatchedInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
|
||||
<< "start with" << *cmd.instance.id;
|
||||
if (deadFallback) {
|
||||
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
|
||||
|
||||
qCInfo(logBare) << "Some instances on other displays match:";
|
||||
if (!deadInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "Some dead instances 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 {
|
||||
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
|
||||
|
||||
if (!deadInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "Some dead instances match:";
|
||||
|
||||
for (auto& instance: deadInstances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
for (auto& instance: deadInstances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
} else if (instances.length() != 1) {
|
||||
if (logHelp) {
|
||||
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
|
||||
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
|
||||
|
||||
for (auto& instance: instances) {
|
||||
qCInfo(logBare).noquote()
|
||||
<< " -" << instance.instance.instanceId << (instance.pid == -1 ? "(dead)" : "");
|
||||
}
|
||||
for (auto& instance: instances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
|
||||
<< (instance.pid == -1 ? " (dead)" : "");
|
||||
}
|
||||
|
||||
return -1;
|
||||
@@ -254,7 +230,7 @@ int selectInstance(
|
||||
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] =
|
||||
auto [liveInstances, deadInstances] =
|
||||
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
|
||||
|
||||
auto instances = liveInstances;
|
||||
@@ -268,29 +244,16 @@ int selectInstance(
|
||||
);
|
||||
|
||||
if (instances.isEmpty()) {
|
||||
if (logHelp) {
|
||||
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
qCInfo(logBare) << "Dead instances:";
|
||||
sortInstances(deadInstances, cmd.config.newest);
|
||||
for (auto& instance: deadInstances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
} else if (!mismatchedInstances.isEmpty()) {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath
|
||||
<< " present on the current display" << getDisplayConnection();
|
||||
|
||||
qCInfo(logBare) << "Some instances on other displays match:";
|
||||
|
||||
for (auto& instance: mismatchedInstances) {
|
||||
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
|
||||
<< instance.instance.display << ')';
|
||||
}
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
qCInfo(logBare) << "Dead instances:";
|
||||
sortInstances(deadInstances, cmd.config.newest);
|
||||
for (auto& instance: deadInstances) {
|
||||
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
|
||||
}
|
||||
} else {
|
||||
qCInfo(logBare) << "No running instances for" << configFilePath;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -351,7 +314,7 @@ int listInstances(CommandState& cmd) {
|
||||
path = QDir(basePath->filePath("by-path")).filePath(pathId);
|
||||
}
|
||||
|
||||
auto [liveInstances, mismatchedInstances, deadInstances] = QsPaths::collectInstances(
|
||||
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
|
||||
path,
|
||||
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
|
||||
);
|
||||
@@ -473,7 +436,7 @@ int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
|
||||
|
||||
{
|
||||
InstanceLockInfo info;
|
||||
if (cmd.misc.noDuplicate && selectInstance(cmd, &info, false, false) == 0) {
|
||||
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
|
||||
qCInfo(logBare) << "An instance of this configuration is already running.";
|
||||
return 0;
|
||||
}
|
||||
@@ -584,17 +547,16 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
}
|
||||
|
||||
QString getDisplayConnection() {
|
||||
// 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 platform = qEnvironmentVariable("QT_QPA_PLATFORM");
|
||||
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
|
||||
auto xDisplay = qEnvironmentVariable("DISPLAY");
|
||||
|
||||
if (!wlDisplay.isEmpty()) {
|
||||
return "wayland/" + wlDisplay;
|
||||
} else if (!xDisplay.isEmpty()) {
|
||||
return "x11/" + xDisplay;
|
||||
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
|
||||
return "wayland," + wlDisplay;
|
||||
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
|
||||
return "x11," + xDisplay;
|
||||
} else {
|
||||
return "unk";
|
||||
return "unk," + QGuiApplication::platformName();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-23
@@ -76,7 +76,6 @@ 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;
|
||||
@@ -90,30 +89,25 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
if (line.startsWith("//@ pragma ")) {
|
||||
auto pragma = line.sliced(11).trimmed();
|
||||
|
||||
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 (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('=');
|
||||
|
||||
if (splitIdx == -1) {
|
||||
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
|
||||
return -1;
|
||||
}
|
||||
|
||||
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 ")) {
|
||||
auto var = envPragma.sliced(0, splitIdx).trimmed();
|
||||
auto val = envPragma.sliced(splitIdx + 1).trimmed();
|
||||
pragmas.envOverrides.insert(var, val);
|
||||
} else if (pragma.startsWith("AppId ")) {
|
||||
pragmas.appId = pragma.sliced(6).trimmed();
|
||||
} else if (pragma.startsWith("ShellId ")) {
|
||||
shellId = pragma.sliced(8).trimmed();
|
||||
@@ -180,10 +174,6 @@ 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());
|
||||
}
|
||||
|
||||
+20
-35
@@ -27,28 +27,20 @@ namespace {
|
||||
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||
#if CRASH_HANDLER
|
||||
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
|
||||
auto dumpPid = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
|
||||
|
||||
if (!lastInfoFdStr.isEmpty()) {
|
||||
auto lastInfoFd = lastInfoFdStr.toInt();
|
||||
|
||||
RelaunchInfo info;
|
||||
|
||||
{
|
||||
QFile file;
|
||||
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||
qFatal() << "Failed to open crash info fd. Cannot restart.";
|
||||
}
|
||||
|
||||
file.seek(0);
|
||||
|
||||
auto ds = QDataStream(&file);
|
||||
ds >> info;
|
||||
QFile file;
|
||||
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
|
||||
qFatal() << "Failed to open crash info fd. Cannot restart.";
|
||||
}
|
||||
|
||||
qunsetenv("__QUICKSHELL_CRASH_INFO_FD");
|
||||
qunsetenv("__QUICKSHELL_CRASH_DUMP_PID");
|
||||
qunsetenv("__QUICKSHELL_CRASH_SIGNAL");
|
||||
file.seek(0);
|
||||
|
||||
auto ds = QDataStream(&file);
|
||||
RelaunchInfo info;
|
||||
ds >> info;
|
||||
|
||||
LogManager::init(
|
||||
!info.noColor,
|
||||
@@ -58,7 +50,8 @@ void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
|
||||
info.logRules
|
||||
);
|
||||
|
||||
qCritical().nospace() << "Quickshell has crashed under pid " << dumpPid
|
||||
qCritical().nospace() << "Quickshell has crashed under pid "
|
||||
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
|
||||
<< " (Coredumps will be available under that pid.)";
|
||||
|
||||
qCritical() << "Further crash information is stored under"
|
||||
@@ -91,29 +84,21 @@ void exitDaemon(int code) {
|
||||
|
||||
close(DAEMON_PIPE);
|
||||
|
||||
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;
|
||||
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";
|
||||
}
|
||||
|
||||
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) != STDOUT_FILENO) { // NOLINT
|
||||
qFatal() << "Failed to open /dev/null on stdout";
|
||||
}
|
||||
|
||||
if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT
|
||||
qCritical().nospace() << "Failed to set daemon stdout 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, 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) {
|
||||
|
||||
@@ -3,10 +3,8 @@ 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
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
@@ -46,7 +45,4 @@ 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
|
||||
+2
-17
@@ -7,14 +7,12 @@
|
||||
#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 or @@WiredDevice.
|
||||
/// The @@type property may be used to determine if this device is a @@WifiDevice.
|
||||
class NetworkDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
@@ -22,17 +20,11 @@ class NetworkDevice: public QObject {
|
||||
// clang-format off
|
||||
/// The device type.
|
||||
///
|
||||
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
|
||||
/// When the device type is `Wired`, the device object is a @@WiredDevice.
|
||||
/// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network
|
||||
/// 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.
|
||||
@@ -53,10 +45,6 @@ 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; }
|
||||
@@ -81,9 +69,6 @@ signals:
|
||||
void nmManagedChanged();
|
||||
void autoconnectChanged();
|
||||
|
||||
protected:
|
||||
ObjectModel<Network> mNetworks {this};
|
||||
|
||||
private:
|
||||
DeviceType::Enum mType;
|
||||
// clang-format off
|
||||
|
||||
@@ -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 disconnected");
|
||||
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant diconnected");
|
||||
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,7 +50,6 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,6 @@ public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Wifi = 1,
|
||||
Wired = 2,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceType::Enum type);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
name = "Quickshell.Networking"
|
||||
description = "Network API"
|
||||
headers = [
|
||||
"qml.hpp",
|
||||
"network.hpp",
|
||||
"device.hpp",
|
||||
"wifi.hpp",
|
||||
"wired.hpp",
|
||||
"enums.hpp",
|
||||
"nm/settings.hpp",
|
||||
]
|
||||
|
||||
+65
-4
@@ -6,9 +6,12 @@
|
||||
#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 {
|
||||
@@ -17,10 +20,68 @@ namespace {
|
||||
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
Network::Network(QString name, NetworkDevice* device, QObject* parent)
|
||||
: QObject(parent)
|
||||
, bName(std::move(name))
|
||||
, mDevice(device) {
|
||||
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)) {
|
||||
this->bStateChanging.setBinding([this] {
|
||||
auto state = this->bState.value();
|
||||
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
|
||||
|
||||
+143
-17
@@ -7,14 +7,147 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
///! A network.
|
||||
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
|
||||
@@ -25,13 +158,11 @@ class Network: public QObject {
|
||||
|
||||
// clang-format off
|
||||
/// The name of the 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.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
/// A list of NetworkManager connnection settings profiles for this network.
|
||||
///
|
||||
/// > [!WARNING] Only valid for the NetworkManager backend.
|
||||
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
|
||||
Q_PROPERTY(QList<NMSettings*> nmSettings READ nmSettings 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.
|
||||
@@ -43,7 +174,7 @@ class Network: public QObject {
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
|
||||
explicit Network(QString name, QObject* parent = nullptr);
|
||||
/// Attempt to connect to the network.
|
||||
///
|
||||
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
|
||||
@@ -63,9 +194,7 @@ public:
|
||||
void settingsRemoved(NMSettings* settings);
|
||||
|
||||
// clang-format off
|
||||
[[nodiscard]] QString name() const { return this->bName; }
|
||||
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
|
||||
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
|
||||
[[nodiscard]] QString name() const { return this->mName; }
|
||||
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
|
||||
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||
@@ -79,7 +208,6 @@ 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();
|
||||
@@ -91,17 +219,15 @@ 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
|
||||
@@ -29,16 +29,6 @@ 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
|
||||
@@ -77,10 +67,8 @@ 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}
|
||||
)
|
||||
@@ -91,4 +79,3 @@ 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)
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../qml.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.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 {
|
||||
@@ -131,7 +133,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -146,9 +147,8 @@ 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
|
||||
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
|
||||
emit this->deviceAdded(dev->frontend());
|
||||
});
|
||||
|
||||
this->registerFrontendDevice(type, dev);
|
||||
}
|
||||
} else {
|
||||
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
|
||||
@@ -160,6 +160,67 @@ 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());
|
||||
}
|
||||
@@ -174,7 +235,7 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
|
||||
this->mDevices.erase(iter);
|
||||
if (dev) {
|
||||
qCDebug(logNetworkManager) << "Device removed:" << path.path();
|
||||
emit this->deviceRemoved(dev->frontend());
|
||||
this->removeFrontendDevice(dev);
|
||||
delete dev;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../qml.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
@@ -70,8 +70,11 @@ 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);
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
|
||||
#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 {
|
||||
@@ -53,29 +49,6 @@ 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);
|
||||
@@ -84,22 +57,6 @@ 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();
|
||||
|
||||
@@ -203,9 +160,4 @@ 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
|
||||
@@ -8,11 +8,9 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
@@ -24,13 +22,6 @@ 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 {
|
||||
@@ -53,23 +44,16 @@ 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& specificObjectPath
|
||||
const QDBusObjectPath& apPath
|
||||
);
|
||||
void networkAdded(Network* net);
|
||||
void networkRemoved(Network* net);
|
||||
void settingsLoaded(NMSettings* settings);
|
||||
void settingsRemoved(NMSettings* settings);
|
||||
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
|
||||
@@ -82,17 +66,12 @@ 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);
|
||||
@@ -114,7 +93,6 @@ 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");
|
||||
@@ -124,7 +102,6 @@ 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;
|
||||
|
||||
@@ -183,23 +183,6 @@ 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 {
|
||||
|
||||
@@ -1,310 +0,0 @@
|
||||
#include "network.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qpointer.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../enums.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "active_connection.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMNetwork::NMNetwork(QObject* parent)
|
||||
: QObject(parent)
|
||||
, bKnown(false)
|
||||
, bReason(NMConnectionStateReason::None)
|
||||
, bState(NMConnectionState::Deactivated) {}
|
||||
|
||||
void NMNetwork::updateReferenceSettings() {
|
||||
// If the network has no connections, the reference is nullptr.
|
||||
if (this->mSettings.isEmpty()) {
|
||||
this->bReferenceSettings = nullptr;
|
||||
return;
|
||||
};
|
||||
|
||||
// If the network has an active connection, use its settings as the reference.
|
||||
if (this->mActiveConnection) {
|
||||
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
|
||||
if (settings && settings != this->bReferenceSettings) {
|
||||
this->bReferenceSettings = settings;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the settings responsible for the last successful connection.
|
||||
NMSettings* selectedSettings = nullptr;
|
||||
quint64 selectedTimestamp = 0;
|
||||
for (auto* settings: this->mSettings.values()) {
|
||||
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
|
||||
if (!selectedSettings || timestamp > selectedTimestamp) {
|
||||
selectedSettings = settings;
|
||||
selectedTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->bReferenceSettings != selectedSettings) {
|
||||
this->bReferenceSettings = selectedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
void NMNetwork::addSettings(NMSettings* settings) {
|
||||
if (this->mSettings.contains(settings->path())) return;
|
||||
this->mSettings.insert(settings->path(), settings);
|
||||
|
||||
auto onDestroyed = [this, settings]() {
|
||||
if (this->mSettings.take(settings->path())) {
|
||||
this->updateReferenceSettings();
|
||||
if (this->mSettings.isEmpty()) this->bKnown = false;
|
||||
// Deletes `this`
|
||||
emit this->settingsRemoved(settings);
|
||||
}
|
||||
};
|
||||
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
|
||||
this->bKnown = true;
|
||||
this->updateReferenceSettings();
|
||||
emit this->settingsAdded(settings);
|
||||
};
|
||||
|
||||
void NMNetwork::addActiveConnection(NMActiveConnection* active) {
|
||||
if (this->mActiveConnection) return;
|
||||
this->mActiveConnection = active;
|
||||
|
||||
this->bState.setBinding([active]() { return active->state(); });
|
||||
this->bReason.setBinding([active]() { return active->stateReason(); });
|
||||
auto onDestroyed = [this, active]() {
|
||||
if (this->mActiveConnection && this->mActiveConnection == active) {
|
||||
this->mActiveConnection = nullptr;
|
||||
this->updateReferenceSettings();
|
||||
this->bState = NMConnectionState::Deactivated;
|
||||
this->bReason = NMConnectionStateReason::None;
|
||||
}
|
||||
};
|
||||
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
|
||||
this->updateReferenceSettings();
|
||||
};
|
||||
|
||||
void NMNetwork::forget() {
|
||||
if (this->mSettings.isEmpty()) return;
|
||||
for (auto* conn: this->mSettings.values()) {
|
||||
conn->forget();
|
||||
}
|
||||
}
|
||||
|
||||
void NMNetwork::bindFrontend(Network* frontend) {
|
||||
auto translateState = [this]() { return this->state() == NMConnectionState::Activated; };
|
||||
frontend->bindableConnected().setBinding(translateState);
|
||||
frontend->bindableKnown().setBinding([this]() { return this->known(); });
|
||||
frontend->bindableState().setBinding([this]() {
|
||||
return static_cast<ConnectionState::Enum>(this->state());
|
||||
});
|
||||
frontend->bindableStateChanging().setBinding([this]() {
|
||||
auto s = static_cast<ConnectionState::Enum>(this->state());
|
||||
return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting;
|
||||
});
|
||||
|
||||
QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() {
|
||||
if (this->reason() == NMConnectionStateReason::DeviceDisconnected) {
|
||||
auto deviceReason = this->deviceFailReason();
|
||||
if (deviceReason == NMDeviceStateReason::NoSecrets)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::NoSecrets);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed);
|
||||
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
|
||||
if (deviceReason == NMDeviceStateReason::SsidNotFound)
|
||||
emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
frontend,
|
||||
&Network::requestConnectWithSettings,
|
||||
this,
|
||||
[this](NMSettings* settings) {
|
||||
if (settings) {
|
||||
emit this->requestActivateConnection(settings->path());
|
||||
return;
|
||||
}
|
||||
qCInfo(
|
||||
logNetworkManager
|
||||
) << "Failed to connectWithSettings: The provided settings no longer exist.";
|
||||
}
|
||||
);
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget);
|
||||
QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect);
|
||||
QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded);
|
||||
QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent)
|
||||
: NMNetwork(parent)
|
||||
, mFrontend(new Network(std::move(name), device, this)) {
|
||||
// Regiter and bind the frontend Network.
|
||||
this->bindFrontend();
|
||||
}
|
||||
|
||||
void NMGenericNetwork::bindFrontend() {
|
||||
auto* frontend = this->mFrontend;
|
||||
this->NMNetwork::bindFrontend(frontend);
|
||||
QObject::connect(frontend, &Network::requestConnect, this, [this]() {
|
||||
if (auto* settingsRef = this->referenceSettings()) {
|
||||
emit this->requestActivateConnection(settingsRef->path());
|
||||
return;
|
||||
}
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent)
|
||||
: NMNetwork(parent)
|
||||
, mSsid(ssid)
|
||||
, bSecurity(WifiSecurityType::Unknown) {
|
||||
|
||||
auto updateSecurity = [this]() {
|
||||
if (NMSettings* settings = this->bReferenceSettings) {
|
||||
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
|
||||
} else if (NMAccessPoint* ap = this->bReferenceAp) {
|
||||
this->bSecurity.setBinding([ap]() { return ap->security(); });
|
||||
} else {
|
||||
this->bSecurity = WifiSecurityType::Unknown;
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
auto checkDisappeared = [this]() {
|
||||
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
|
||||
};
|
||||
|
||||
QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity);
|
||||
QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity);
|
||||
QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared);
|
||||
QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared);
|
||||
|
||||
// Register and bind the frontend WifiNetwork.
|
||||
this->mFrontend = new WifiNetwork(ssid, device, this);
|
||||
this->bindFrontend();
|
||||
}
|
||||
|
||||
void NMWirelessNetwork::updateReferenceAp() {
|
||||
// If the network has no APs, the reference is a nullptr.
|
||||
if (this->mAccessPoints.isEmpty()) {
|
||||
this->bReferenceAp = nullptr;
|
||||
this->bSignalStrength = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the AP with the strongest signal.
|
||||
NMAccessPoint* selectedAp = nullptr;
|
||||
for (auto* ap: this->mAccessPoints.values()) {
|
||||
// Always prefer the active AP.
|
||||
if (ap->path() == this->bActiveApPath) {
|
||||
selectedAp = ap;
|
||||
break;
|
||||
}
|
||||
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
|
||||
selectedAp = ap;
|
||||
}
|
||||
}
|
||||
if (this->bReferenceAp != selectedAp) {
|
||||
this->bReferenceAp = selectedAp;
|
||||
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
|
||||
if (this->mAccessPoints.contains(ap->path())) return;
|
||||
this->mAccessPoints.insert(ap->path(), ap);
|
||||
auto onDestroyed = [this, ap]() {
|
||||
if (this->mAccessPoints.take(ap->path())) {
|
||||
this->updateReferenceAp();
|
||||
// Deletes `this`
|
||||
emit this->apRemoved(ap);
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
|
||||
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
|
||||
// clang-format on
|
||||
this->updateReferenceAp();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::bindFrontend() {
|
||||
auto* frontend = this->mFrontend;
|
||||
this->NMNetwork::bindFrontend(frontend);
|
||||
|
||||
auto translateSignal = [this]() { return this->signalStrength() / 100.0; };
|
||||
frontend->bindableSignalStrength().setBinding(translateSignal);
|
||||
frontend->bindableSecurity().setBinding([this]() { return this->security(); });
|
||||
|
||||
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() {
|
||||
if (auto* settingsRef = this->referenceSettings()) {
|
||||
emit this->requestActivateConnection(settingsRef->path());
|
||||
return;
|
||||
}
|
||||
if (auto* apRef = this->referenceAp()) {
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path());
|
||||
return;
|
||||
}
|
||||
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
|
||||
return;
|
||||
});
|
||||
|
||||
QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) {
|
||||
NMSettingsMap settings;
|
||||
settings["802-11-wireless-security"]["psk"] = psk;
|
||||
if (const QPointer<NMSettings> ref = this->referenceSettings()) {
|
||||
auto* call = ref->updateSettings(settings);
|
||||
QObject::connect(
|
||||
call,
|
||||
&QDBusPendingCallWatcher::finished,
|
||||
this,
|
||||
[this, ref](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
if (reply.isError()) {
|
||||
qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message();
|
||||
} else {
|
||||
if (!ref) {
|
||||
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared.";
|
||||
} else {
|
||||
emit this->requestActivateConnection(ref->path());
|
||||
}
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (auto* apRef = this->referenceAp()) {
|
||||
emit this->requestAddAndActivateConnection(settings, apRef->path());
|
||||
return;
|
||||
}
|
||||
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared.";
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
@@ -1,138 +0,0 @@
|
||||
#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
|
||||
@@ -1,4 +0,0 @@
|
||||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wired">
|
||||
</interface>
|
||||
</node>
|
||||
@@ -37,7 +37,6 @@ 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",
|
||||
|
||||
@@ -273,7 +273,6 @@ 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;
|
||||
};
|
||||
@@ -292,8 +291,7 @@ 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-3-ethernet.cloned-mac-address"
|
||||
|| s == "802-3-ethernet.mac-address")
|
||||
|| s == "802-11-wireless.ssid")
|
||||
{
|
||||
if (value.typeId() == QMetaType::QString) {
|
||||
return value.toString().toUtf8();
|
||||
@@ -425,8 +423,7 @@ 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-3-ethernet.mac-address-blacklist"
|
||||
|| s == "802-3-ethernet.mac-address-denylist" || s == "802-3-ethernet.s390-subchannels")
|
||||
|| s == "802-11-wireless.seen-bssids")
|
||||
{
|
||||
if (value.typeId() == QMetaType::QVariantList) {
|
||||
QStringList stringList;
|
||||
@@ -438,18 +435,6 @@ 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();
|
||||
@@ -513,15 +498,6 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#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
|
||||
@@ -1,53 +0,0 @@
|
||||
#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
|
||||
+291
-28
@@ -1,4 +1,5 @@
|
||||
#include "wireless.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatetime.h>
|
||||
@@ -11,6 +12,7 @@
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qpointer.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
@@ -26,7 +28,6 @@
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
@@ -37,6 +38,143 @@ 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) {
|
||||
@@ -65,10 +203,6 @@ 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() {
|
||||
@@ -80,9 +214,7 @@ 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) {
|
||||
@@ -139,7 +271,7 @@ void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) {
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
|
||||
// Find an existing network with connection settings that matches the active
|
||||
// Find an exisiting 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()) {
|
||||
@@ -232,45 +364,176 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
|
||||
}
|
||||
|
||||
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
|
||||
auto* net = new NMWirelessNetwork(ssid, this->frontend(), this);
|
||||
auto* net = new NMWirelessNetwork(ssid, 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);
|
||||
|
||||
this->mNetworks.insert(ssid, net);
|
||||
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
|
||||
this->mNetworks.insert(ssid, net);
|
||||
this->registerFrontendNetwork(net);
|
||||
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())) {
|
||||
if (net->visible()) emit this->networkRemoved(net->frontend());
|
||||
this->removeFrontendNetwork(net);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "dbus_nm_wireless.h"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
#include "settings.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
@@ -34,9 +33,76 @@ 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), and a frontend WifiDevice.
|
||||
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s).
|
||||
class NMWirelessDevice: public NMDevice {
|
||||
Q_OBJECT;
|
||||
|
||||
@@ -48,11 +114,12 @@ 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);
|
||||
@@ -70,16 +137,17 @@ 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;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
#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
|
||||
@@ -1,151 +0,0 @@
|
||||
#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
|
||||
+213
-240
@@ -73,241 +73,6 @@ 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
|
||||
|
||||
@@ -374,11 +139,219 @@ Scope {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
model: Networking.devices
|
||||
delegate: Component {
|
||||
Loader {
|
||||
required property var modelData
|
||||
width: ListView.view.width
|
||||
sourceComponent: deviceDelegate
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ namespace {
|
||||
QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg);
|
||||
}
|
||||
|
||||
WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent)
|
||||
: Network(std::move(ssid), device, parent) {};
|
||||
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
|
||||
|
||||
void WifiNetwork::connectWithPsk(const QString& psk) {
|
||||
if (this->bConnected) {
|
||||
@@ -43,6 +42,9 @@ 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) {
|
||||
|
||||
+11
-2
@@ -7,6 +7,7 @@
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/doc.hpp"
|
||||
#include "../core/model.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "network.hpp"
|
||||
@@ -26,7 +27,7 @@ class WifiNetwork: public Network {
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent = nullptr);
|
||||
explicit WifiNetwork(QString ssid, 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`.
|
||||
///
|
||||
@@ -60,6 +61,9 @@ 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);
|
||||
@@ -70,6 +74,10 @@ 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);
|
||||
@@ -80,11 +88,12 @@ 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);
|
||||
@@ -1,22 +0,0 @@
|
||||
#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
|
||||
@@ -1,53 +0,0 @@
|
||||
#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
|
||||
@@ -325,8 +325,8 @@ public:
|
||||
void setLoopState(MprisLoopState::Enum loopState);
|
||||
|
||||
[[nodiscard]] qreal rate() const { return this->bRate; }
|
||||
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bMinRate; }
|
||||
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bMaxRate; }
|
||||
[[nodiscard]] QBindable<qreal> bindableMinRate() const { return &this->bRate; }
|
||||
[[nodiscard]] QBindable<qreal> bindableMaxRate() const { return &this->bRate; }
|
||||
void setRate(qreal rate);
|
||||
|
||||
[[nodiscard]] bool shuffle() const { return this->bShuffle; }
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
@@ -194,29 +192,19 @@ uint NotificationServer::Notify(
|
||||
|
||||
notification->updateProperties(appName, appIcon, summary, body, actions, hints, expireTimeout);
|
||||
|
||||
auto id = notification->id();
|
||||
|
||||
if (!old) {
|
||||
emit this->notification(notification);
|
||||
|
||||
if (!notification->isTracked()) {
|
||||
auto closeReason = notification->closeReason();
|
||||
|
||||
// Emit NotificationClosed after Notify returns
|
||||
QMetaObject::invokeMethod(
|
||||
this,
|
||||
[this, id, closeReason]() { emit this->NotificationClosed(id, closeReason); },
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
|
||||
emit this->NotificationClosed(notification->id(), notification->closeReason());
|
||||
delete notification;
|
||||
} else {
|
||||
this->idMap.insert(id, notification);
|
||||
this->idMap.insert(notification->id(), notification);
|
||||
this->mNotifications.insertObject(notification);
|
||||
}
|
||||
}
|
||||
|
||||
return id;
|
||||
return notification->id();
|
||||
}
|
||||
|
||||
} // namespace qs::service::notifications
|
||||
@@ -193,7 +193,7 @@ void PwNode::initProps(const spa_dict* props) {
|
||||
qCCritical(
|
||||
logNode
|
||||
) << this
|
||||
<< "has a device.id property that does not correspond to a device object. Id:" << id;
|
||||
<< "has a device.id property that does not corrospond 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 correspond to everyone else's measurements correctly.
|
||||
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
|
||||
auto linear = *reinterpret_cast<float*>(iter);
|
||||
auto visual = std::cbrt(linear);
|
||||
props.volumes.push_back(visual);
|
||||
@@ -595,45 +595,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -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 receiving a video stream.
|
||||
// A consumer of video, such as a program that is recieving a video stream.
|
||||
//
|
||||
// This is equivalent to the media class `Video/Sink` and is composed of the
|
||||
// @@PwNodeType.Video and @@PwNodeType.Sink flags.
|
||||
|
||||
@@ -245,14 +245,13 @@ void PwPeakStream::handleProcess() {
|
||||
if (!this->formatReady || this->stream == nullptr) return;
|
||||
|
||||
auto* buffer = pw_stream_dequeue_buffer(this->stream);
|
||||
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
|
||||
|
||||
if (buffer == nullptr) {
|
||||
qCWarning(logPeak) << "Peak monitor ran out of buffers.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); });
|
||||
|
||||
auto* spaBuffer = buffer->buffer;
|
||||
if (spaBuffer == nullptr || spaBuffer->n_datas < 1) {
|
||||
return;
|
||||
@@ -278,27 +277,7 @@ void PwPeakStream::handleProcess() {
|
||||
|
||||
QVector<float> volumes;
|
||||
if (auto* audioData = dynamic_cast<PwNodeBoundAudio*>(this->node->boundData)) {
|
||||
// Device volumes don't require inverse scaling
|
||||
if (!this->node->shouldUseDevice()) {
|
||||
const auto& nchannels = audioData->channels();
|
||||
const auto& nvolumes = audioData->volumes();
|
||||
|
||||
for (const auto channel: this->monitor->mChannels) {
|
||||
for (auto i = 0; i != nchannels.length(); i++) {
|
||||
if (nchannels[i] == channel) {
|
||||
volumes.push_back(nvolumes[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (volumes.length() != channelCount) {
|
||||
qCCritical(logPeak) << this->node
|
||||
<< "is missing channels present in capture stream. Node channels:"
|
||||
<< nchannels << "Stream channels:" << this->monitor->mChannels;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!this->node->shouldUseDevice()) volumes = audioData->volumes();
|
||||
}
|
||||
|
||||
this->channelPeaks.fill(0.0f, channelCount);
|
||||
|
||||
@@ -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 corresponds to
|
||||
/// The volumes of each audio channel individually. Each entry corrosponds 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, corresponding to the object's `node.name` property.
|
||||
/// The node's name, corrosponding to the object's `node.name` property.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
/// The node's description, corresponding to the object's `node.description` property.
|
||||
/// The node's description, corrosponding 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, corresponding to the object's `node.nickname` property.
|
||||
/// The node's nickname, corrosponding 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);
|
||||
|
||||
@@ -127,14 +127,14 @@ signals:
|
||||
///
|
||||
/// This may be because the user entered the wrong password or otherwise
|
||||
/// failed to authenticate.
|
||||
/// This signal is not emitted when the user canceled the request or it
|
||||
/// This signal is not emmitted 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();
|
||||
|
||||
/// Emitted when an ongoing authentication request is cancelled by the PolKit daemon.
|
||||
/// Emmitted when on ongoing authentication request is cancelled by the PolKit daemon.
|
||||
void authenticationRequestCancelled();
|
||||
|
||||
void selectedIdentityChanged();
|
||||
|
||||
@@ -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.authenticationSucceeded signal. At this point, the dialog can be closed.
|
||||
If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeeded 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
|
||||
|
||||
@@ -195,7 +195,7 @@ private:
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniIconPixmapList, bAttentionIconPixmaps);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bAttentionMovieName);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, DBusSniTooltip, bTooltip);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu, &StatusNotifierItem::onlyMenuChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bIsMenu);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QDBusObjectPath, bMenuPath);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, bool, bHasMenu, &StatusNotifierItem::hasMenuChanged);
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ if (WAYLAND_SESSION_LOCK)
|
||||
endif()
|
||||
|
||||
if (WAYLAND_TOPLEVEL_MANAGEMENT)
|
||||
add_subdirectory(toplevel)
|
||||
add_subdirectory(toplevel_management)
|
||||
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement)
|
||||
endif()
|
||||
|
||||
|
||||
+122
-428
@@ -1,7 +1,6 @@
|
||||
#include "dmabuf.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -10,7 +9,6 @@
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <EGL/eglplatform.h>
|
||||
#include <GL/gl.h>
|
||||
#include <fcntl.h>
|
||||
#include <gbm.h>
|
||||
@@ -19,7 +17,6 @@
|
||||
#include <private/qrhivulkan_p.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qdir.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
@@ -27,7 +24,7 @@
|
||||
#include <qopenglcontext_platform.h>
|
||||
#include <qpair.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscopeguard.h>
|
||||
#include <qscopedpointer.h>
|
||||
#include <qsgrendererinterface.h>
|
||||
#include <qsgtexture_platform.h>
|
||||
#include <qtypes.h>
|
||||
@@ -36,8 +33,6 @@
|
||||
#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>
|
||||
@@ -59,33 +54,23 @@ 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) {
|
||||
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;
|
||||
// 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)
|
||||
}
|
||||
|
||||
bool drmFormatHasAlpha(uint32_t drmFormat) {
|
||||
@@ -99,13 +84,6 @@ 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) {
|
||||
@@ -133,13 +111,10 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) {
|
||||
return debug;
|
||||
}
|
||||
|
||||
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(); });
|
||||
GbmDeviceHandle::~GbmDeviceHandle() {
|
||||
if (this->device) {
|
||||
MANAGER->unrefGbmDevice(this->device);
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause
|
||||
@@ -316,329 +291,106 @@ LinuxDmabufManager::LinuxDmabufManager(WlBufferManagerPrivate* manager)
|
||||
}
|
||||
}
|
||||
|
||||
bool LinuxDmabufManager::initRenderFormats(QQuickWindow* window) {
|
||||
auto* ri = window->rendererInterface();
|
||||
if (ri->graphicsApi() == QSGRendererInterface::Vulkan) {
|
||||
return this->initRenderFormatsVk(window);
|
||||
} else {
|
||||
return this->initRenderFormatsGl(window);
|
||||
}
|
||||
}
|
||||
|
||||
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); });
|
||||
GbmDeviceHandle LinuxDmabufManager::getGbmDevice(dev_t handle) {
|
||||
struct DrmFree {
|
||||
static void cleanup(drmDevice* d) { drmFreeDevice(&d); }
|
||||
};
|
||||
|
||||
std::string renderNodeStorage;
|
||||
if (drmDev->available_nodes & (1 << DRM_NODE_RENDER)) {
|
||||
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
|
||||
} else if (drmDev->available_nodes & (1 << DRM_NODE_PRIMARY)) {
|
||||
renderNodeStorage = drmDev->nodes[DRM_NODE_PRIMARY]; // NOLINT
|
||||
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 {
|
||||
qCDebug(logDmabuf) << "Cannot create GBM device: DRM device does not have render node.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
renderNodeStorage = drmDev->nodes[DRM_NODE_RENDER]; // NOLINT
|
||||
renderNode = &renderNodeStorage;
|
||||
sharedDevice = std::ranges::find_if(this->gbmDevices, [&](const SharedGbmDevice& d) {
|
||||
return d.renderNode == renderNodeStorage;
|
||||
});
|
||||
}
|
||||
|
||||
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 (sharedDevice != this->gbmDevices.end()) {
|
||||
qCDebug(logDmabuf) << "Used existing GBM device on render node" << *renderNode;
|
||||
++sharedDevice->refcount;
|
||||
return sharedDevice->device;
|
||||
} else {
|
||||
auto fd = open(renderNode->c_str(), O_RDWR | O_CLOEXEC);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
auto* device = gbm_create_device(fd);
|
||||
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;
|
||||
|
||||
if (!device) {
|
||||
qCDebug(logDmabuf) << "Failed to create GBM device from render node"
|
||||
<< renderNodeStorage.c_str();
|
||||
qCDebug(logDmabuf) << "Lost reference to GBM device" << device;
|
||||
|
||||
if (--iter->refcount == 0) {
|
||||
auto fd = gbm_device_get_fd(iter->device);
|
||||
gbm_device_destroy(iter->device);
|
||||
close(fd);
|
||||
return nullptr;
|
||||
|
||||
this->gbmDevices.erase(iter);
|
||||
qCDebug(logDmabuf) << "Destroyed GBM device" << device;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(logDmabuf) << "Created GBM device on render node" << renderNodeStorage.c_str();
|
||||
GbmDeviceHandle LinuxDmabufManager::dupHandle(const GbmDeviceHandle& handle) {
|
||||
if (!handle) return GbmDeviceHandle();
|
||||
|
||||
auto shared = std::make_shared<GbmDevice>(handle, std::move(renderNodeStorage), device);
|
||||
this->gbmDevices.push_back(shared);
|
||||
return shared;
|
||||
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);
|
||||
}
|
||||
|
||||
WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
|
||||
@@ -648,67 +400,16 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
|
||||
}
|
||||
|
||||
LinuxDmabufFormatSelection formats;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -737,7 +438,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(const WlBufferRequest& request) {
|
||||
}
|
||||
|
||||
WlBuffer* LinuxDmabufManager::createDmabuf(
|
||||
const std::shared_ptr<GbmDevice>& device,
|
||||
GbmDeviceHandle& device,
|
||||
uint32_t format,
|
||||
const LinuxDmabufModifiers& modifiers,
|
||||
uint32_t width,
|
||||
@@ -756,22 +457,15 @@ 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...";
|
||||
buffer->usedImplicitModifier = true;
|
||||
bo = gbm_bo_create(device->device, width, height, format, flags);
|
||||
bo = gbm_bo_create(*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,
|
||||
@@ -799,6 +493,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
|
||||
if (plane.fd < 0) {
|
||||
qCritical(logDmabuf) << "Failed to get gbm_bo fd for plane" << i << qt_error_string(plane.fd);
|
||||
params.destroy();
|
||||
gbm_bo_destroy(bo);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -819,7 +514,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf(
|
||||
params.create_immed(static_cast<int32_t>(width), static_cast<int32_t>(height), format, 0);
|
||||
params.destroy();
|
||||
|
||||
buffer->device = device;
|
||||
buffer->device = this->dupHandle(device);
|
||||
buffer->width = width;
|
||||
buffer->height = height;
|
||||
buffer->format = format;
|
||||
@@ -866,9 +561,8 @@ 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.implicit && this->usedImplicitModifier)
|
||||
&& (format.modifiers.isEmpty()
|
||||
|| std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end());
|
||||
});
|
||||
|
||||
@@ -1120,7 +814,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 success. Without dup, WlDmaBuffer would double-close.
|
||||
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
|
||||
const int dupFd =
|
||||
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
if (dupFd < 0) {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <gbm.h>
|
||||
@@ -73,16 +72,28 @@ private:
|
||||
QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc);
|
||||
QDebug& operator<<(QDebug& debug, const FourCCModStr& fourcc);
|
||||
|
||||
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);
|
||||
class GbmDeviceHandle {
|
||||
public:
|
||||
GbmDeviceHandle() = default;
|
||||
GbmDeviceHandle(gbm_device* device): device(device) {}
|
||||
|
||||
dev_t handle = 0;
|
||||
std::string renderNode;
|
||||
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:
|
||||
gbm_device* device = nullptr;
|
||||
};
|
||||
|
||||
@@ -161,7 +172,7 @@ private:
|
||||
uint32_t stride = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<GbmDevice> device;
|
||||
GbmDeviceHandle device;
|
||||
gbm_bo* bo = nullptr;
|
||||
wl_buffer* mBuffer = nullptr;
|
||||
int planeCount = 0;
|
||||
@@ -170,7 +181,6 @@ 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);
|
||||
@@ -229,31 +239,33 @@ public:
|
||||
[[nodiscard]] WlBuffer* createDmabuf(const WlBufferRequest& request);
|
||||
|
||||
[[nodiscard]] WlBuffer* createDmabuf(
|
||||
const std::shared_ptr<GbmDevice>& device,
|
||||
GbmDeviceHandle& device,
|
||||
uint32_t format,
|
||||
const LinuxDmabufModifiers& modifiers,
|
||||
uint32_t width,
|
||||
uint32_t height
|
||||
);
|
||||
|
||||
bool initRenderFormats(QQuickWindow* window);
|
||||
|
||||
private:
|
||||
bool initRenderFormatsGl(QQuickWindow* window);
|
||||
bool initRenderFormatsVk(QQuickWindow* window);
|
||||
struct SharedGbmDevice {
|
||||
dev_t handle = 0;
|
||||
std::string renderNode;
|
||||
gbm_device* device = nullptr;
|
||||
qsizetype refcount = 0;
|
||||
};
|
||||
|
||||
void feedbackDone();
|
||||
|
||||
std::shared_ptr<GbmDevice> getGbmDevice(dev_t handle);
|
||||
GbmDeviceHandle getGbmDevice(dev_t handle);
|
||||
void unrefGbmDevice(gbm_device* device);
|
||||
GbmDeviceHandle dupHandle(const GbmDeviceHandle& handle);
|
||||
|
||||
LinuxDmabufFormatSelection renderFormats;
|
||||
std::string renderNode;
|
||||
QList<LinuxDmabufTranche> tranches;
|
||||
QList<std::weak_ptr<GbmDevice>> gbmDevices;
|
||||
QList<SharedGbmDevice> gbmDevices;
|
||||
WlBufferManagerPrivate* manager;
|
||||
|
||||
friend class LinuxDmabufFeedback;
|
||||
friend struct GbmDevice;
|
||||
friend class GbmDeviceHandle;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer::dmabuf
|
||||
@@ -1,8 +1,5 @@
|
||||
|
||||
#include "manager.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#include <drm_fourcc.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
@@ -14,7 +11,6 @@
|
||||
#include <qvectornd.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../window/proxywindow.hpp"
|
||||
#include "dmabuf.hpp"
|
||||
#include "manager_p.hpp"
|
||||
#include "qsg.hpp"
|
||||
@@ -26,25 +22,12 @@ 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) || noReuse) {
|
||||
if (!buffer || !buffer->isCompatible(request)) {
|
||||
buffer.reset(WlBufferManager::instance()->createBuffer(request));
|
||||
if (newBuffer) *newBuffer = true;
|
||||
}
|
||||
@@ -61,36 +44,7 @@ WlBufferManager* WlBufferManager::instance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
bool WlBufferManager::isReady() const { return this->p->mReady; }
|
||||
|
||||
[[nodiscard]] WlBuffer* WlBufferManager::createBuffer(const WlBufferRequest& request) {
|
||||
static const bool dmabufDisabled = qEnvironmentVariableIsSet("QS_DISABLE_DMABUF");
|
||||
@@ -100,16 +54,11 @@ bool WlBufferManager::isReady() const {
|
||||
qCDebug(logBuffer).nospace() << " Dmabuf requests on device " << request.dmabuf.device
|
||||
<< " (disabled: " << dmabufDisabled << ')';
|
||||
|
||||
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& [format, modifiers]: request.dmabuf.formats) {
|
||||
qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format)
|
||||
<< (modifiers.length() == 0 ? " (No modifiers specified)" : "");
|
||||
|
||||
if (format.implicit) {
|
||||
qCDebug(logBuffer) << " Implicit Modifier";
|
||||
}
|
||||
|
||||
for (const auto& modifier: format.modifiers) {
|
||||
for (const auto& modifier: modifiers) {
|
||||
qCDebug(logBuffer) << " Explicit Modifier" << dmabuf::FourCCModStr(modifier);
|
||||
}
|
||||
}
|
||||
@@ -125,7 +74,7 @@ bool WlBufferManager::isReady() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!dmabufDisabled && !this->p->mRenderFormatsFailed) {
|
||||
if (!dmabufDisabled) {
|
||||
if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf;
|
||||
qCWarning(logBuffer) << "DMA buffer creation failed, falling back to SHM.";
|
||||
}
|
||||
@@ -138,8 +87,8 @@ WlBufferManagerPrivate::WlBufferManagerPrivate(WlBufferManager* manager)
|
||||
, dmabuf(this) {}
|
||||
|
||||
void WlBufferManagerPrivate::dmabufReady() {
|
||||
this->mDmabufFormatsReady = true;
|
||||
if (this->mRenderFormatsReady) emit this->manager->ready();
|
||||
this->mReady = true;
|
||||
emit this->manager->ready();
|
||||
}
|
||||
|
||||
WlBufferQSGDisplayNode::WlBufferQSGDisplayNode(QQuickWindow* window)
|
||||
|
||||
@@ -55,12 +55,9 @@ 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 {
|
||||
@@ -126,7 +123,6 @@ public:
|
||||
|
||||
static WlBufferManager* instance();
|
||||
|
||||
void initWindow(QQuickWindow* window);
|
||||
[[nodiscard]] bool isReady() const;
|
||||
[[nodiscard]] WlBuffer* createBuffer(const WlBufferRequest& request);
|
||||
|
||||
|
||||
@@ -14,9 +14,7 @@ public:
|
||||
WlBufferManager* manager;
|
||||
dmabuf::LinuxDmabufManager dmabuf;
|
||||
|
||||
bool mDmabufFormatsReady = false;
|
||||
bool mRenderFormatsReady = false;
|
||||
bool mRenderFormatsFailed = false;
|
||||
bool mReady = false;
|
||||
};
|
||||
|
||||
} // namespace qs::wayland::buffer
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "../../../core/logcat.hpp"
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../toplevel/wlr_toplevel.hpp"
|
||||
#include "../../toplevel_management/handle.hpp"
|
||||
#include "hyprland_toplevel.hpp"
|
||||
#include "monitor.hpp"
|
||||
#include "toplevel_mapping.hpp"
|
||||
@@ -74,23 +74,10 @@ HyprlandIpc::HyprlandIpc() {
|
||||
|
||||
// clang-format on
|
||||
|
||||
this->makeRequest("j/status", [&, this](bool success, QByteArray resp) {
|
||||
if (success) {
|
||||
this->bUsingLua = [&]() {
|
||||
if (resp == "unknown request") return false;
|
||||
auto json = QJsonDocument::fromJson(resp).object();
|
||||
auto provider = json.value("configProvider");
|
||||
return provider == "lua";
|
||||
}();
|
||||
} else {
|
||||
qCWarning(logHyprlandIpc) << "Hyprland ipc status request failed.";
|
||||
}
|
||||
|
||||
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
|
||||
this->refreshMonitors(true);
|
||||
this->refreshWorkspaces(true);
|
||||
this->refreshToplevels();
|
||||
});
|
||||
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
|
||||
this->refreshMonitors(true);
|
||||
this->refreshWorkspaces(true);
|
||||
this->refreshToplevels();
|
||||
}
|
||||
|
||||
QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; }
|
||||
@@ -139,10 +126,11 @@ void HyprlandIpc::eventSocketReady() {
|
||||
}
|
||||
|
||||
void HyprlandIpc::toplevelAddressed(
|
||||
wayland::toplevel::wlr::ToplevelHandle* handle,
|
||||
wayland::toplevel_management::impl::ToplevelHandle* handle,
|
||||
quint64 address
|
||||
) {
|
||||
auto* waylandToplevel = wayland::toplevel::ToplevelManager::instance()->forImpl(handle);
|
||||
auto* waylandToplevel =
|
||||
wayland::toplevel_management::ToplevelManager::instance()->forImpl(handle);
|
||||
|
||||
if (!waylandToplevel) return;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../../core/streamreader.hpp"
|
||||
#include "../../../wayland/toplevel/wlr_toplevel.hpp"
|
||||
#include "../../../wayland/toplevel_management/handle.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
@@ -81,8 +81,6 @@ public:
|
||||
|
||||
[[nodiscard]] HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen);
|
||||
|
||||
[[nodiscard]] QBindable<bool> bindableUsingLua() const { return &this->bUsingLua; }
|
||||
|
||||
[[nodiscard]] QBindable<HyprlandMonitor*> bindableFocusedMonitor() const {
|
||||
return &this->bFocusedMonitor;
|
||||
}
|
||||
@@ -118,7 +116,6 @@ signals:
|
||||
void connected();
|
||||
void rawEvent(HyprlandIpcEvent* event);
|
||||
|
||||
void usingLuaChanged();
|
||||
void focusedMonitorChanged();
|
||||
void focusedWorkspaceChanged();
|
||||
void activeToplevelChanged();
|
||||
@@ -128,7 +125,10 @@ private slots:
|
||||
void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void eventSocketReady();
|
||||
|
||||
void toplevelAddressed(qs::wayland::toplevel::wlr::ToplevelHandle* handle, quint64 address);
|
||||
void toplevelAddressed(
|
||||
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
|
||||
quint64 address
|
||||
);
|
||||
|
||||
void onFocusedMonitorDestroyed();
|
||||
|
||||
@@ -155,8 +155,6 @@ private:
|
||||
|
||||
HyprlandIpcEvent event {this};
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandIpc, bool, bUsingLua, &HyprlandIpc::usingLuaChanged);
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
HyprlandIpc,
|
||||
HyprlandMonitor*,
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../toplevel/qml.hpp"
|
||||
#include "../../toplevel_management/qml.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "toplevel_mapping.hpp"
|
||||
#include "workspace.hpp"
|
||||
|
||||
using namespace qs::wayland::toplevel;
|
||||
using namespace qs::wayland::toplevel_management;
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
@@ -112,7 +112,7 @@ Toplevel* HyprlandToplevel::waylandHandle() {
|
||||
return ToplevelManager::instance()->forImpl(this->mWaylandHandle);
|
||||
}
|
||||
|
||||
void HyprlandToplevel::setWaylandHandle(wlr::ToplevelHandle* handle) {
|
||||
void HyprlandToplevel::setWaylandHandle(impl::ToplevelHandle* handle) {
|
||||
if (this->mWaylandHandle == handle) return;
|
||||
if (this->mWaylandHandle) {
|
||||
QObject::disconnect(this->mWaylandHandle, nullptr, this, nullptr);
|
||||
|
||||
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user