From a7ff484793ef69c7f97a99de2bec02640d681e83 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Thu, 3 Jul 2025 13:06:21 -0500 Subject: [PATCH 01/32] init: NetworkManager service --- CMakeLists.txt | 3 +- src/services/CMakeLists.txt | 4 ++ src/services/networkmanager/CMakeLists.txt | 42 ++++++++++++++ src/services/networkmanager/core.cpp | 57 +++++++++++++++++++ src/services/networkmanager/core.hpp | 21 +++++++ .../org.freedesktop.NetworkManager.Device.xml | 16 ++++++ .../org.freedesktop.NetworkManager.xml | 20 +++++++ 7 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/services/networkmanager/CMakeLists.txt create mode 100644 src/services/networkmanager/core.cpp create mode 100644 src/services/networkmanager/core.hpp create mode 100644 src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml create mode 100644 src/services/networkmanager/org.freedesktop.NetworkManager.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 7161c4e4..7c827c3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ boption(X11 "X11" ON) boption(I3 "I3/Sway" ON) boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3) boption(SERVICE_STATUS_NOTIFIER "System Tray" ON) +boption(SERVICE_NETWORKMANAGER "NetworkManager" ON) boption(SERVICE_PIPEWIRE "PipeWire" ON) boption(SERVICE_MPRIS "Mpris" ON) boption(SERVICE_PAM "Pam" ON) @@ -117,7 +118,7 @@ if (WAYLAND) list(APPEND QT_FPDEPS WaylandClient) endif() -if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH) +if (SERVICE.NETWORKMANAGER OR SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH) set(DBUS ON) endif() diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 5ab5c550..13198bb7 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -25,3 +25,7 @@ endif() if (SERVICE_NOTIFICATIONS) add_subdirectory(notifications) endif() + +if (SERVICE_NETWORKMANAGER) + add_subdirectory(networkmanager) +endif() diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt new file mode 100644 index 00000000..d7843847 --- /dev/null +++ b/src/services/networkmanager/CMakeLists.txt @@ -0,0 +1,42 @@ +set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES + CLASSNAME DBusNetworkManagerService + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.xml + dbus_service +) + +set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES + CLASSNAME DBusNetworkManagerDevice + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.xml + dbus_device +) + +qt_add_library(quickshell-service-networkmanager STATIC + core.cpp + ${DBUS_INTERFACES} +) + +# dbus headers +target_include_directories(quickshell-service-networkmanager PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +qt_add_qml_module(quickshell-service-networkmanager + URI Quickshell.Services.NetworkManager + VERSION 0.1 + DEPENDENCIES QtQml +) + +qs_add_module_deps_light(quickshell-service-networkmanager NetworkManager) +install_qml_module(quickshell-service-networkmanager) + +target_link_libraries(quickshell-service-networkmanager PRIVATE Qt::Qml Qt::DBus) +qs_add_link_dependencies(quickshell-service-networkmanager quickshell-dbus) +target_link_libraries(quickshell PRIVATE quickshell-service-networkmanagerplugin) + +qs_module_pch(quickshell-service-networkmanager SET dbus) diff --git a/src/services/networkmanager/core.cpp b/src/services/networkmanager/core.cpp new file mode 100644 index 00000000..3f8129e8 --- /dev/null +++ b/src/services/networkmanager/core.cpp @@ -0,0 +1,57 @@ +#include "core.hpp" + +#include + +#include "../../dbus/bus.hpp" +#include "dbus_service.h" + +namespace qs::service::networkmanager { + +const QString NM_SERVICE = "org.freedesktop.NetworkManager"; +const QString NM_PATH = "/org/freedesktop/NetworkManager"; + +namespace { +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.service.networkmanager", QtWarningMsg); +} + +NetworkManager::NetworkManager() { + qCDebug(logNetworkManager) << "Starting NetworkManager Service"; + + auto bus = QDBusConnection::systemBus(); + if (!bus.isConnected()) { + qCWarning(logNetworkManager + ) << "Could not connect to DBus. NetworkManager service will not work."; + return; + } + + this->service = new DBusNetworkManagerService(NM_SERVICE, NM_PATH, bus, this); + + if (!this->service->isValid()) { + qCDebug(logNetworkManager + ) << "NetworkManager service is not currently running, attempting to start it."; + + dbus::tryLaunchService(this, bus, NM_SERVICE, [this](bool success) { + if (success) { + qCDebug(logNetworkManager) << "Successfully launched NetworkManager service."; + this->init(); + } else { + qCWarning(logNetworkManager) + << "Could not start NetworkManager. The NetworkManager service will not work."; + } + }); + } else { + this->init(); + } +} + +void NetworkManager::init() {} + +NetworkManager* NetworkManager::instance() { + static NetworkManager* instance = nullptr; + if (!instance) { + instance = new NetworkManager(); + } + return instance; +} + +} // namespace qs::service::networkmanager diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp new file mode 100644 index 00000000..5f5ee007 --- /dev/null +++ b/src/services/networkmanager/core.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +#include "dbus_service.h" + +namespace qs::service::networkmanager { + +class NetworkManager: public QObject { + Q_OBJECT; + +public: + static NetworkManager* instance(); + +private: + explicit NetworkManager(); + void init(); + DBusNetworkManagerService* service = nullptr; +}; + +} // namespace qs::service::networkmanager diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml new file mode 100644 index 00000000..dbd3be2c --- /dev/null +++ b/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.xml new file mode 100644 index 00000000..473ff329 --- /dev/null +++ b/src/services/networkmanager/org.freedesktop.NetworkManager.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + From e92a9ec6e9fc32dcebca9dc4951d0f86eb89eb3f Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Thu, 3 Jul 2025 20:06:28 -0500 Subject: [PATCH 02/32] fix: CMake typo --- src/services/networkmanager/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt index d7843847..d04c091f 100644 --- a/src/services/networkmanager/CMakeLists.txt +++ b/src/services/networkmanager/CMakeLists.txt @@ -32,7 +32,7 @@ qt_add_qml_module(quickshell-service-networkmanager DEPENDENCIES QtQml ) -qs_add_module_deps_light(quickshell-service-networkmanager NetworkManager) +qs_add_module_deps_light(quickshell-service-networkmanager Quickshell) install_qml_module(quickshell-service-networkmanager) target_link_libraries(quickshell-service-networkmanager PRIVATE Qt::Qml Qt::DBus) From 66fa5dc76eae9d165529583070de786c34ace6a8 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Thu, 3 Jul 2025 20:06:58 -0500 Subject: [PATCH 03/32] feat: NetworkManager.state --- src/services/networkmanager/core.cpp | 84 +++++++++++++++- src/services/networkmanager/core.hpp | 95 +++++++++++++++++++ .../org.freedesktop.NetworkManager.xml | 5 +- 3 files changed, 180 insertions(+), 4 deletions(-) diff --git a/src/services/networkmanager/core.cpp b/src/services/networkmanager/core.cpp index 3f8129e8..803552fe 100644 --- a/src/services/networkmanager/core.cpp +++ b/src/services/networkmanager/core.cpp @@ -1,8 +1,15 @@ #include "core.hpp" +#include +#include +#include +#include +#include +#include #include #include "../../dbus/bus.hpp" +#include "../../dbus/properties.hpp" #include "dbus_service.h" namespace qs::service::networkmanager { @@ -14,6 +21,14 @@ namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.service.networkmanager", QtWarningMsg); } +QString NetworkManagerState::toString(NetworkManagerState::Enum state) { + auto metaEnum = QMetaEnum::fromType(); + if (metaEnum.valueToKey(state)) { + return QString(metaEnum.valueToKey(state)); + } + return "Invalid state"; +} + NetworkManager::NetworkManager() { qCDebug(logNetworkManager) << "Starting NetworkManager Service"; @@ -44,7 +59,55 @@ NetworkManager::NetworkManager() { } } -void NetworkManager::init() {} +void NetworkManager::init() { + // QObject::connect( + // this->service, + // &DBusNetworkManagerService::DeviceAdded, + // this, + // &NetworkManager::onDeviceAdded + // ); + // + // QObject::connect( + // this->service, + // &DBusNetworkManagerService::DeviceRemoved, + // this, + // &NetworkManager::onDeviceRemoved + // ); + + this->serviceProperties.setInterface(this->service); + this->serviceProperties.updateAllViaGetAll(); + + // this->registerDevices(); +} + +// void NetworkManager::registerDevices() { +// auto pending = this->service->GetDevices(); +// auto* call = new QDBusPendingCallWatcher(pending, this); +// +// auto responseCallback = [this](QDBusPendingCallWatcher* call) { +// const QDBusPendingReply> reply = *call; +// +// if (reply.isError()) { +// qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); +// } else { +// for (const QDBusObjectPath& devicePath: reply.value()) { +// qCDebug(logNetworkManager) << "Device added:" << devicePath.path(); +// } +// } +// +// delete call; +// }; +// +// QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +// } +// +// void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { +// qCDebug(logNetworkManager) << "Device added:" << path; +// } +// +// void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { +// qCDebug(logNetworkManager) << "Device removed:" << path; +// } NetworkManager* NetworkManager::instance() { static NetworkManager* instance = nullptr; @@ -54,4 +117,23 @@ NetworkManager* NetworkManager::instance() { return instance; } +NetworkManagerQml::NetworkManagerQml(QObject* parent): QObject(parent) { + QObject::connect( + NetworkManager::instance(), + &NetworkManager::stateChanged, + this, + &NetworkManagerQml::stateChanged + ); +} + } // namespace qs::service::networkmanager + +namespace qs::dbus { +using namespace qs::service::networkmanager; + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp index 5f5ee007..77aa59ff 100644 --- a/src/services/networkmanager/core.hpp +++ b/src/services/networkmanager/core.hpp @@ -1,21 +1,116 @@ #pragma once + +#include +#include +#include +#include #include +#include +#include #include +#include +#include "../../dbus/properties.hpp" #include "dbus_service.h" namespace qs::service::networkmanager { +class NetworkManagerState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Asleep = 10, + Disconnected = 20, + Disconnecting = 30, + Connecting = 40, + ConnectedLocal = 50, + ConnectedSite = 60, + ConnectedGlobal = 70, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(qs::service::networkmanager::NetworkManagerState::Enum state); +}; + +} // namespace qs::service::networkmanager + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::service::networkmanager::NetworkManagerState::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::service::networkmanager { + class NetworkManager: public QObject { Q_OBJECT; public: + [[nodiscard]] QBindable bindableState() const { + return &this->bState; + }; + static NetworkManager* instance(); +signals: + void stateChanged(); + +private slots: + // void onDeviceAdded(const QDBusObjectPath& path); + // void onDeviceRemoved(const QDBusObjectPath& path); + private: explicit NetworkManager(); + void init(); + void registerDevices(); + void registerDevice(const QString& path); + + Q_OBJECT_BINDABLE_PROPERTY( + NetworkManager, + NetworkManagerState::Enum, + bState, + &NetworkManager::stateChanged + ); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, serviceProperties); + QS_DBUS_PROPERTY_BINDING(NetworkManager, pState, bState, serviceProperties, "State"); + DBusNetworkManagerService* service = nullptr; }; +///! Provides access to the NetworkManager service. +/// An interface to the [NetworkManager daemon], which can be used to +/// view and configure network interfaces and connections. +/// +/// > [!NOTE] The NetworkManager daemon must be installed to use this service. +/// +/// [NetworkManager daemon]: https://networkmanager.dev +class NetworkManagerQml: public QObject { + Q_OBJECT; + QML_NAMED_ELEMENT(NetworkManager); + QML_SINGLETON; + // clang-format off + Q_PROPERTY(NetworkManagerState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + // clang-format on + +public: + explicit NetworkManagerQml(QObject* parent = nullptr); + + [[nodiscard]] static QBindable bindableState() { + return NetworkManager::instance()->bindableState(); + } + +signals: + void stateChanged(); +}; + } // namespace qs::service::networkmanager diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.xml index 473ff329..4f9ca03e 100644 --- a/src/services/networkmanager/org.freedesktop.NetworkManager.xml +++ b/src/services/networkmanager/org.freedesktop.NetworkManager.xml @@ -7,14 +7,13 @@ + + - - - From c3e1280139d7bf0681dcd47136f992c6e7aebef5 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Sat, 5 Jul 2025 22:07:27 -0500 Subject: [PATCH 04/32] feat: devices, wifiDevice, device properties --- src/services/networkmanager/CMakeLists.txt | 1 + src/services/networkmanager/core.cpp | 134 +++++++++++------- src/services/networkmanager/core.hpp | 24 +++- src/services/networkmanager/device.cpp | 86 +++++++++++ src/services/networkmanager/device.hpp | 157 +++++++++++++++++++++ src/services/upower/device.cpp | 7 - 6 files changed, 349 insertions(+), 60 deletions(-) create mode 100644 src/services/networkmanager/device.cpp create mode 100644 src/services/networkmanager/device.hpp diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt index d04c091f..2deebf85 100644 --- a/src/services/networkmanager/CMakeLists.txt +++ b/src/services/networkmanager/CMakeLists.txt @@ -20,6 +20,7 @@ qt_add_dbus_interface(DBUS_INTERFACES qt_add_library(quickshell-service-networkmanager STATIC core.cpp + device.cpp ${DBUS_INTERFACES} ) diff --git a/src/services/networkmanager/core.cpp b/src/services/networkmanager/core.cpp index 803552fe..e91dd5fc 100644 --- a/src/services/networkmanager/core.cpp +++ b/src/services/networkmanager/core.cpp @@ -8,6 +8,7 @@ #include #include +#include "../../core/model.hpp" #include "../../dbus/bus.hpp" #include "../../dbus/properties.hpp" #include "dbus_service.h" @@ -60,69 +61,102 @@ NetworkManager::NetworkManager() { } void NetworkManager::init() { - // QObject::connect( - // this->service, - // &DBusNetworkManagerService::DeviceAdded, - // this, - // &NetworkManager::onDeviceAdded - // ); - // - // QObject::connect( - // this->service, - // &DBusNetworkManagerService::DeviceRemoved, - // this, - // &NetworkManager::onDeviceRemoved - // ); + QObject::connect( + this->service, + &DBusNetworkManagerService::DeviceAdded, + this, + &NetworkManager::onDeviceAdded + ); + + QObject::connect( + this->service, + &DBusNetworkManagerService::DeviceRemoved, + this, + &NetworkManager::onDeviceRemoved + ); this->serviceProperties.setInterface(this->service); this->serviceProperties.updateAllViaGetAll(); - // this->registerDevices(); + this->registerDevices(); } -// void NetworkManager::registerDevices() { -// auto pending = this->service->GetDevices(); -// auto* call = new QDBusPendingCallWatcher(pending, this); -// -// auto responseCallback = [this](QDBusPendingCallWatcher* call) { -// const QDBusPendingReply> reply = *call; -// -// if (reply.isError()) { -// qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); -// } else { -// for (const QDBusObjectPath& devicePath: reply.value()) { -// qCDebug(logNetworkManager) << "Device added:" << devicePath.path(); -// } -// } -// -// delete call; -// }; -// -// QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -// } -// -// void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { -// qCDebug(logNetworkManager) << "Device added:" << path; -// } -// -// void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { -// qCDebug(logNetworkManager) << "Device removed:" << path; -// } +void NetworkManager::registerDevices() { + auto pending = this->service->GetDevices(); + auto* call = new QDBusPendingCallWatcher(pending, this); -NetworkManager* NetworkManager::instance() { - static NetworkManager* instance = nullptr; - if (!instance) { - instance = new NetworkManager(); + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply> reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); + } else { + for (const QDBusObjectPath& devicePath: reply.value()) { + this->registerDevice(devicePath.path()); + } + } + + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { + this->registerDevice(path.path()); +} + +void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { + auto iter = this->mDeviceHash.find(path.path()); + + if (iter == this->mDeviceHash.end()) { + qCWarning(logNetworkManager) << "NetworkManager service sent removal signal for" << path.path() + << "which is not registered."; + } else { + auto* device = iter.value(); + this->mDeviceHash.erase(iter); + this->mDevices.removeObject(device); + qCDebug(logNetworkManager) << "NetworkManagerDevice" << device->path() << "removed."; + } +} + +void NetworkManager::registerDevice(const QString& path) { + if (this->mDeviceHash.contains(path)) { + qCDebug(logNetworkManager) << "Skipping duplicate registration of NetworkManagerDevice" << path; + return; } + + auto* device = new NetworkManagerDevice(this); + device->init(path); + + if (!device->isValid()) { + qCWarning(logNetworkManager) << "Ignoring invalid NetworkManagerDevice registration of" << path; + delete device; + return; + } + if (device->bindableType().value() == NetworkManagerDeviceType::Wifi) { + mWifiDevice = device; + } + + this->mDeviceHash.insert(path, device); + this->mDevices.insertObject(device); + qCDebug(logNetworkManager) << "Registered NetworkManagerDevice" << path; +} + +ObjectModel* NetworkManager::devices() { return &this->mDevices; } +NetworkManagerDevice* NetworkManager::wifiDevice() { return this->mWifiDevice; } + +NetworkManager* NetworkManager::instance() { + static NetworkManager* instance = new NetworkManager(); // NOLINT return instance; } NetworkManagerQml::NetworkManagerQml(QObject* parent): QObject(parent) { QObject::connect( - NetworkManager::instance(), - &NetworkManager::stateChanged, - this, - &NetworkManagerQml::stateChanged + NetworkManager::instance(), + &NetworkManager::stateChanged, + this, + &NetworkManagerQml::stateChanged ); } diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp index 77aa59ff..7b1790aa 100644 --- a/src/services/networkmanager/core.hpp +++ b/src/services/networkmanager/core.hpp @@ -10,8 +10,10 @@ #include #include +#include "../../core/model.hpp" #include "../../dbus/properties.hpp" #include "dbus_service.h" +#include "device.hpp" namespace qs::service::networkmanager { @@ -54,6 +56,8 @@ class NetworkManager: public QObject { Q_OBJECT; public: + [[nodiscard]] NetworkManagerDevice* wifiDevice(); + [[nodiscard]] ObjectModel* devices(); [[nodiscard]] QBindable bindableState() const { return &this->bState; }; @@ -64,15 +68,19 @@ class NetworkManager: public QObject { void stateChanged(); private slots: - // void onDeviceAdded(const QDBusObjectPath& path); - // void onDeviceRemoved(const QDBusObjectPath& path); + void onDeviceAdded(const QDBusObjectPath& path); + void onDeviceRemoved(const QDBusObjectPath& path); private: explicit NetworkManager(); void init(); - void registerDevices(); void registerDevice(const QString& path); + void registerDevices(); + + QHash mDeviceHash; + ObjectModel mDevices {this}; + NetworkManagerDevice* mWifiDevice = nullptr; Q_OBJECT_BINDABLE_PROPERTY( NetworkManager, @@ -98,12 +106,22 @@ class NetworkManagerQml: public QObject { Q_OBJECT; QML_NAMED_ELEMENT(NetworkManager); QML_SINGLETON; + Q_PROPERTY(qs::service::networkmanager::NetworkManagerDevice* wifiDevice READ wifiDevice); + QSDOC_TYPE_OVERRIDE(ObjectModel*); + Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); // clang-format off Q_PROPERTY(NetworkManagerState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); // clang-format on public: explicit NetworkManagerQml(QObject* parent = nullptr); + [[nodiscard]] static NetworkManagerDevice* wifiDevice() { + return NetworkManager::instance()->wifiDevice(); + } + + [[nodiscard]] static ObjectModel* devices() { + return NetworkManager::instance()->devices(); + } [[nodiscard]] static QBindable bindableState() { return NetworkManager::instance()->bindableState(); diff --git a/src/services/networkmanager/device.cpp b/src/services/networkmanager/device.cpp new file mode 100644 index 00000000..a2e7c5f7 --- /dev/null +++ b/src/services/networkmanager/device.cpp @@ -0,0 +1,86 @@ + +#include "device.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_device.h" + +using namespace qs::dbus; + +namespace qs::service::networkmanager { + +namespace { +Q_LOGGING_CATEGORY( + logNetworkManagerDevice, + "quickshell.service.networkmanager.device", + QtWarningMsg +); +} + +QString NetworkManagerDeviceState::toString(NetworkManagerDeviceState::Enum state) { + auto metaEnum = QMetaEnum::fromType(); + if (metaEnum.valueToKey(state)) { + return QString(metaEnum.valueToKey(state)); + } + return "Invalid state"; +} + +QString NetworkManagerDeviceType::toString(NetworkManagerDeviceType::Enum type) { + auto metaEnum = QMetaEnum::fromType(); + if (metaEnum.valueToKey(type)) { + return QString(metaEnum.valueToKey(type)); + } + return "Invalid type"; +} + +NetworkManagerDevice::NetworkManagerDevice(QObject* parent): QObject(parent) {} + +void NetworkManagerDevice::init(const QString& path) { + this->device = new DBusNetworkManagerDevice( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->device->isValid()) { + qCWarning(logNetworkManagerDevice) << "Cannot create NetworkManagerDevice for" << path; + return; + } + + this->deviceProperties.setInterface(this->device); + this->deviceProperties.updateAllViaGetAll(); +} + +bool NetworkManagerDevice::isValid() const { return this->device && this->device->isValid(); } +QString NetworkManagerDevice::address() const { + return this->device ? this->device->service() : QString(); +} +QString NetworkManagerDevice::path() const { + return this->device ? this->device->path() : QString(); +} + +} // namespace qs::service::networkmanager + +namespace qs::dbus { + +using namespace qs::service::networkmanager; + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/services/networkmanager/device.hpp b/src/services/networkmanager/device.hpp new file mode 100644 index 00000000..ed9b3549 --- /dev/null +++ b/src/services/networkmanager/device.hpp @@ -0,0 +1,157 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_device.h" + +namespace qs::service::networkmanager { + +class NetworkManagerDeviceType: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Ethernet = 1, + Wifi = 2, + Unused1 = 3, + Unused2 = 4, + Bluetooth = 5, + OLPCMesh = 6, + WiMAX = 7, + Modem = 8, + InfiniBand = 9, + Bond = 10, + VLAN = 11, + ADSL = 12, + Bridge = 13, + Team = 14, + TUN = 16, + Tunnel = 17, + MACVLAN = 18, + VXLAN = 19, + VETH = 20, + MACsec = 21, + Dummy = 22, + PPP = 23, + OVSInterface = 24, + OVSPort = 25, + OVSBridge = 26, + WPAN = 27, + SixLoWPAN = 28, + WireGuard = 29, + WifiP2P = 30, + VRF = 31, + Loopback = 32, + HSR = 33, + IPVLAN = 34, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NetworkManagerDeviceType::Enum type); +}; + +class NetworkManagerDeviceState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Unmanaged = 10, + Unavailable = 20, + Disconnected = 30, + Prepare = 40, + Config = 50, + NeedAuth = 60, + IPConfig = 70, + IPCheck = 80, + Secondaries = 90, + Activated = 100, + Deactivating = 110, + Failed = 120, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NetworkManagerDeviceState::Enum state); +}; + +} // namespace qs::service::networkmanager + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::service::networkmanager::NetworkManagerDeviceState::Enum; + static DBusResult fromWire(Wire wire); +}; + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::service::networkmanager::NetworkManagerDeviceType::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::service::networkmanager { + +class NetworkManagerDevice: public QObject { + Q_OBJECT; + // clang-format off + Q_PROPERTY(NetworkManagerDeviceType::Enum type READ default NOTIFY typeChanged BINDABLE bindableType); + Q_PROPERTY(NetworkManagerDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + Q_PROPERTY(QString interface READ default NOTIFY interfaceChanged BINDABLE bindableInterface); + Q_PROPERTY(bool managed READ default NOTIFY managedChanged BINDABLE bindableManaged); + // clang-format on + QML_ELEMENT; + QML_UNCREATABLE("NetworkManagerDevices can only be acquired from NetworkManager"); + +public: + explicit NetworkManagerDevice(QObject* parent = nullptr); + + void init(const QString& path); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + + [[nodiscard]] QBindable bindableType() const { + return &this->bType; + }; + [[nodiscard]] QBindable bindableState() const { + return &this->bState; + }; + [[nodiscard]] QBindable bindableManaged() const { return &this->bManaged; }; + [[nodiscard]] QBindable bindableInterface() const { return &this->bInterface; }; + +signals: + void typeChanged(); + void stateChanged(); + void interfaceChanged(); + void managedChanged(); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, NetworkManagerDeviceType::Enum, bType, &NetworkManagerDevice::typeChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, NetworkManagerDeviceState::Enum, bState, &NetworkManagerDevice::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, bool, bManaged, &NetworkManagerDevice::managedChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, QString, bInterface, &NetworkManagerDevice::interfaceChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManagerDevice, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pType, bType, deviceProperties, "DeviceType"); + QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pState, bState, deviceProperties, "State"); + QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pManaged, bManaged, deviceProperties, "Managed"); + QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pInterface, bInterface, deviceProperties, "Interface"); + // clang-format on + + DBusNetworkManagerDevice* device = nullptr; +}; + +} // namespace qs::service::networkmanager diff --git a/src/services/upower/device.cpp b/src/services/upower/device.cpp index b7c61e12..8055ccf1 100644 --- a/src/services/upower/device.cpp +++ b/src/services/upower/device.cpp @@ -84,13 +84,6 @@ void UPowerDevice::init(const QString& path) { return; } - QObject::connect( - &this->deviceProperties, - &DBusPropertyGroup::getAllFinished, - this, - &UPowerDevice::onGetAllFinished - ); - this->deviceProperties.setInterface(this->device); this->deviceProperties.updateAllViaGetAll(); } From 6ee3cb54a32b19ac2a8ba70d85c1882a8cc08324 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Sat, 5 Jul 2025 22:13:48 -0500 Subject: [PATCH 05/32] hotfix: revert unintended change to upower --- src/services/upower/device.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/services/upower/device.cpp b/src/services/upower/device.cpp index 8055ccf1..b7c61e12 100644 --- a/src/services/upower/device.cpp +++ b/src/services/upower/device.cpp @@ -84,6 +84,13 @@ void UPowerDevice::init(const QString& path) { return; } + QObject::connect( + &this->deviceProperties, + &DBusPropertyGroup::getAllFinished, + this, + &UPowerDevice::onGetAllFinished + ); + this->deviceProperties.setInterface(this->device); this->deviceProperties.updateAllViaGetAll(); } From 8679f79d32fa86dcc0547c19c6c3c3f5966a14fc Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 12:36:10 -0500 Subject: [PATCH 06/32] feat(nix): nm buildInput --- default.nix | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/default.nix b/default.nix index 73cd8d16..33250cc9 100644 --- a/default.nix +++ b/default.nix @@ -20,6 +20,7 @@ libdrm, libgbm ? null, pipewire, + networkmanager, pam, gitRev ? (let @@ -40,6 +41,7 @@ withWayland ? true, withX11 ? true, withPipewire ? true, + withNetworkManager ? true, withPam ? true, withHyprland ? true, withI3 ? true, @@ -70,7 +72,8 @@ ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ] ++ lib.optional withX11 xorg.libxcb ++ lib.optional withPam pam - ++ lib.optional withPipewire pipewire; + ++ lib.optional withPipewire pipewire + ++ lib.optional withNetworkManager networkmanager; cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; @@ -84,6 +87,7 @@ (lib.cmakeBool "WAYLAND" withWayland) (lib.cmakeBool "SCREENCOPY" (libgbm != null)) (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire) + (lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager) (lib.cmakeBool "SERVICE_PAM" withPam) (lib.cmakeBool "HYPRLAND" withHyprland) (lib.cmakeBool "I3" withI3) From 3c32d881d7369cb6eebfbc626bc9537d5d11b6a2 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 13:07:00 -0500 Subject: [PATCH 07/32] test: device basics --- src/services/networkmanager/test/nm.qml | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/services/networkmanager/test/nm.qml diff --git a/src/services/networkmanager/test/nm.qml b/src/services/networkmanager/test/nm.qml new file mode 100644 index 00000000..cea56de2 --- /dev/null +++ b/src/services/networkmanager/test/nm.qml @@ -0,0 +1,32 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Services.NetworkManager + +FloatingWindow { + color: contentItem.palette.window + ListView { + anchors.fill: parent + anchors.margins: 5 + model: NetworkManager.devices + delegate: WrapperRectangle { + width: parent.width + color: "transparent" + border.color: palette.button + border.width: 1 + margin: 5 + + ColumnLayout { + Label { + text: `Device ${index}: ${modelData.interface}` + font.bold: true + } + Label { text: "Type: " + NetworkManagerDeviceType.toString(modelData.type) } + Label { text: "State: " + NetworkManagerDeviceState.toString(modelData.state) } + Label { text: `Managed: ${modelData.managed}` } + } + } + } +} From 0391e027b00d32b3723155cffe27eaec3b05b6bb Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 13:16:34 -0500 Subject: [PATCH 08/32] test: nm state --- src/services/networkmanager/test/nm.qml | 52 +++++++++++++++++-------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/services/networkmanager/test/nm.qml b/src/services/networkmanager/test/nm.qml index cea56de2..0b46d740 100644 --- a/src/services/networkmanager/test/nm.qml +++ b/src/services/networkmanager/test/nm.qml @@ -7,25 +7,43 @@ import Quickshell.Services.NetworkManager FloatingWindow { color: contentItem.palette.window - ListView { + + ColumnLayout { anchors.fill: parent - anchors.margins: 5 - model: NetworkManager.devices - delegate: WrapperRectangle { - width: parent.width - color: "transparent" - border.color: palette.button - border.width: 1 - margin: 5 - - ColumnLayout { - Label { - text: `Device ${index}: ${modelData.interface}` - font.bold: true + anchors.margins: 10 + spacing: 10 + + + ColumnLayout { + Label { + text: "NetworkManager Service Test" + font.bold: true + } + + Label { text: "Current state: " + NetworkManagerState.toString(NetworkManager.state) } + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + model: NetworkManager.devices + + delegate: WrapperRectangle { + width: parent.width + color: "transparent" + border.color: palette.button + border.width: 1 + margin: 10 + + ColumnLayout { + Label { + text: `Device ${index}: ${modelData.interface}` + font.bold: true + } + Label { text: "Type: " + NetworkManagerDeviceType.toString(modelData.type) } + Label { text: "State: " + NetworkManagerDeviceState.toString(modelData.state) } + Label { text: `Managed: ${modelData.managed}` } } - Label { text: "Type: " + NetworkManagerDeviceType.toString(modelData.type) } - Label { text: "State: " + NetworkManagerDeviceState.toString(modelData.state) } - Label { text: `Managed: ${modelData.managed}` } } } } From fdbdf1d4f2ab7349ca0fcac6ba4bc277fade4db8 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 13:36:40 -0500 Subject: [PATCH 09/32] fix: devicetype enum matches NMDeviceType in spec --- src/services/networkmanager/device.hpp | 42 +++++++++++++------------ src/services/networkmanager/test/nm.qml | 6 ++-- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/services/networkmanager/device.hpp b/src/services/networkmanager/device.hpp index ed9b3549..8d59263c 100644 --- a/src/services/networkmanager/device.hpp +++ b/src/services/networkmanager/device.hpp @@ -17,39 +17,41 @@ class NetworkManagerDeviceType: public QObject { public: enum Enum : quint8 { + Unknown = 0, + Generic = 14, Ethernet = 1, Wifi = 2, Unused1 = 3, Unused2 = 4, Bluetooth = 5, - OLPCMesh = 6, - WiMAX = 7, + OlpcMesh = 6, + Wimax = 7, Modem = 8, - InfiniBand = 9, + Infiniband = 9, Bond = 10, - VLAN = 11, - ADSL = 12, + Vlan = 11, + Adsl = 12, Bridge = 13, - Team = 14, - TUN = 16, + Team = 15, + Tun = 16, Tunnel = 17, - MACVLAN = 18, - VXLAN = 19, - VETH = 20, - MACsec = 21, + Macvlan = 18, + Vxlan = 19, + Veth = 20, + Macsec = 21, Dummy = 22, - PPP = 23, - OVSInterface = 24, - OVSPort = 25, - OVSBridge = 26, - WPAN = 27, - SixLoWPAN = 28, + Ppp = 23, + OvsInterface = 24, + OvsPort = 25, + OvsBridge = 26, + Wpan = 27, + Lowpan = 28, WireGuard = 29, WifiP2P = 30, - VRF = 31, + Vrf = 31, Loopback = 32, - HSR = 33, - IPVLAN = 34, + Hsr = 33, + Ipvlan = 34, }; Q_ENUM(Enum); Q_INVOKABLE static QString toString(NetworkManagerDeviceType::Enum type); diff --git a/src/services/networkmanager/test/nm.qml b/src/services/networkmanager/test/nm.qml index 0b46d740..91e83d07 100644 --- a/src/services/networkmanager/test/nm.qml +++ b/src/services/networkmanager/test/nm.qml @@ -20,7 +20,7 @@ FloatingWindow { font.bold: true } - Label { text: "Current state: " + NetworkManagerState.toString(NetworkManager.state) } + Label { text: "Current state: " + NMState.toString(NetworkManager.state) } } ListView { @@ -40,8 +40,8 @@ FloatingWindow { text: `Device ${index}: ${modelData.interface}` font.bold: true } - Label { text: "Type: " + NetworkManagerDeviceType.toString(modelData.type) } - Label { text: "State: " + NetworkManagerDeviceState.toString(modelData.state) } + Label { text: "Type: " + NMState.toString(modelData.type) } + Label { text: "State: " + NMState.toString(modelData.state) } Label { text: `Managed: ${modelData.managed}` } } } From 208cfd001fc2668b2b00c40825448d12c135dc10 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 13:56:59 -0500 Subject: [PATCH 10/32] style: shorten to NM --- src/services/networkmanager/CMakeLists.txt | 2 +- src/services/networkmanager/core.cpp | 16 +++---- src/services/networkmanager/core.hpp | 17 +++---- src/services/networkmanager/device.cpp | 36 +++++++-------- src/services/networkmanager/device.hpp | 52 +++++++++++----------- 5 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt index 2deebf85..aa38a3b7 100644 --- a/src/services/networkmanager/CMakeLists.txt +++ b/src/services/networkmanager/CMakeLists.txt @@ -9,7 +9,7 @@ qt_add_dbus_interface(DBUS_INTERFACES ) set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNetworkManagerDevice + CLASSNAME DBusNMDevice NO_NAMESPACE TRUE ) diff --git a/src/services/networkmanager/core.cpp b/src/services/networkmanager/core.cpp index e91dd5fc..2b5a86fc 100644 --- a/src/services/networkmanager/core.cpp +++ b/src/services/networkmanager/core.cpp @@ -116,35 +116,35 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { auto* device = iter.value(); this->mDeviceHash.erase(iter); this->mDevices.removeObject(device); - qCDebug(logNetworkManager) << "NetworkManagerDevice" << device->path() << "removed."; + qCDebug(logNetworkManager) << "NMDevice" << device->path() << "removed."; } } void NetworkManager::registerDevice(const QString& path) { if (this->mDeviceHash.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of NetworkManagerDevice" << path; + qCDebug(logNetworkManager) << "Skipping duplicate registration of NMDevice" << path; return; } - auto* device = new NetworkManagerDevice(this); + auto* device = new NMDevice(this); device->init(path); if (!device->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid NetworkManagerDevice registration of" << path; + qCWarning(logNetworkManager) << "Ignoring invalid NMDevice registration of" << path; delete device; return; } - if (device->bindableType().value() == NetworkManagerDeviceType::Wifi) { + if (device->bindableType().value() == NMDeviceType::Wifi) { mWifiDevice = device; } this->mDeviceHash.insert(path, device); this->mDevices.insertObject(device); - qCDebug(logNetworkManager) << "Registered NetworkManagerDevice" << path; + qCDebug(logNetworkManager) << "Registered NMDevice" << path; } -ObjectModel* NetworkManager::devices() { return &this->mDevices; } -NetworkManagerDevice* NetworkManager::wifiDevice() { return this->mWifiDevice; } +ObjectModel* NetworkManager::devices() { return &this->mDevices; } +NMDevice* NetworkManager::wifiDevice() { return this->mWifiDevice; } NetworkManager* NetworkManager::instance() { static NetworkManager* instance = new NetworkManager(); // NOLINT diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp index 7b1790aa..c0fc4fc5 100644 --- a/src/services/networkmanager/core.hpp +++ b/src/services/networkmanager/core.hpp @@ -56,8 +56,8 @@ class NetworkManager: public QObject { Q_OBJECT; public: - [[nodiscard]] NetworkManagerDevice* wifiDevice(); - [[nodiscard]] ObjectModel* devices(); + [[nodiscard]] NMDevice* wifiDevice(); + [[nodiscard]] ObjectModel* devices(); [[nodiscard]] QBindable bindableState() const { return &this->bState; }; @@ -76,11 +76,12 @@ private slots: void init(); void registerDevice(const QString& path); + void createDevice(const QString& path); void registerDevices(); - QHash mDeviceHash; - ObjectModel mDevices {this}; - NetworkManagerDevice* mWifiDevice = nullptr; + QHash mDeviceHash; + ObjectModel mDevices {this}; + NMDevice* mWifiDevice = nullptr; Q_OBJECT_BINDABLE_PROPERTY( NetworkManager, @@ -106,7 +107,7 @@ class NetworkManagerQml: public QObject { Q_OBJECT; QML_NAMED_ELEMENT(NetworkManager); QML_SINGLETON; - Q_PROPERTY(qs::service::networkmanager::NetworkManagerDevice* wifiDevice READ wifiDevice); + Q_PROPERTY(qs::service::networkmanager::NMDevice* wifiDevice READ wifiDevice); QSDOC_TYPE_OVERRIDE(ObjectModel*); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); // clang-format off @@ -115,11 +116,11 @@ class NetworkManagerQml: public QObject { public: explicit NetworkManagerQml(QObject* parent = nullptr); - [[nodiscard]] static NetworkManagerDevice* wifiDevice() { + [[nodiscard]] static NMDevice* wifiDevice() { return NetworkManager::instance()->wifiDevice(); } - [[nodiscard]] static ObjectModel* devices() { + [[nodiscard]] static ObjectModel* devices() { return NetworkManager::instance()->devices(); } diff --git a/src/services/networkmanager/device.cpp b/src/services/networkmanager/device.cpp index a2e7c5f7..2b9a9ae4 100644 --- a/src/services/networkmanager/device.cpp +++ b/src/services/networkmanager/device.cpp @@ -18,32 +18,32 @@ namespace qs::service::networkmanager { namespace { Q_LOGGING_CATEGORY( - logNetworkManagerDevice, + logNMDevice, "quickshell.service.networkmanager.device", QtWarningMsg ); } -QString NetworkManagerDeviceState::toString(NetworkManagerDeviceState::Enum state) { - auto metaEnum = QMetaEnum::fromType(); +QString NMDeviceState::toString(NMDeviceState::Enum state) { + auto metaEnum = QMetaEnum::fromType(); if (metaEnum.valueToKey(state)) { return QString(metaEnum.valueToKey(state)); } return "Invalid state"; } -QString NetworkManagerDeviceType::toString(NetworkManagerDeviceType::Enum type) { - auto metaEnum = QMetaEnum::fromType(); +QString NMDeviceType::toString(NMDeviceType::Enum type) { + auto metaEnum = QMetaEnum::fromType(); if (metaEnum.valueToKey(type)) { return QString(metaEnum.valueToKey(type)); } return "Invalid type"; } -NetworkManagerDevice::NetworkManagerDevice(QObject* parent): QObject(parent) {} +NMDevice::NMDevice(QObject* parent): QObject(parent) {} -void NetworkManagerDevice::init(const QString& path) { - this->device = new DBusNetworkManagerDevice( +void NMDevice::init(const QString& path) { + this->device = new DBusNMDevice( "org.freedesktop.NetworkManager", path, QDBusConnection::systemBus(), @@ -51,7 +51,7 @@ void NetworkManagerDevice::init(const QString& path) { ); if (!this->device->isValid()) { - qCWarning(logNetworkManagerDevice) << "Cannot create NetworkManagerDevice for" << path; + qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; return; } @@ -59,11 +59,11 @@ void NetworkManagerDevice::init(const QString& path) { this->deviceProperties.updateAllViaGetAll(); } -bool NetworkManagerDevice::isValid() const { return this->device && this->device->isValid(); } -QString NetworkManagerDevice::address() const { +bool NMDevice::isValid() const { return this->device && this->device->isValid(); } +QString NMDevice::address() const { return this->device ? this->device->service() : QString(); } -QString NetworkManagerDevice::path() const { +QString NMDevice::path() const { return this->device ? this->device->path() : QString(); } @@ -73,14 +73,14 @@ namespace qs::dbus { using namespace qs::service::networkmanager; -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); } -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); } } // namespace qs::dbus diff --git a/src/services/networkmanager/device.hpp b/src/services/networkmanager/device.hpp index 8d59263c..aabeb321 100644 --- a/src/services/networkmanager/device.hpp +++ b/src/services/networkmanager/device.hpp @@ -10,7 +10,7 @@ namespace qs::service::networkmanager { -class NetworkManagerDeviceType: public QObject { +class NMDeviceType: public QObject { Q_OBJECT; QML_ELEMENT; QML_SINGLETON; @@ -54,10 +54,10 @@ class NetworkManagerDeviceType: public QObject { Ipvlan = 34, }; Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkManagerDeviceType::Enum type); + Q_INVOKABLE static QString toString(NMDeviceType::Enum type); }; -class NetworkManagerDeviceState: public QObject { +class NMDeviceState: public QObject { Q_OBJECT; QML_ELEMENT; QML_SINGLETON; @@ -79,7 +79,7 @@ class NetworkManagerDeviceState: public QObject { Failed = 120, }; Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkManagerDeviceState::Enum state); + Q_INVOKABLE static QString toString(NMDeviceState::Enum state); }; } // namespace qs::service::networkmanager @@ -87,16 +87,16 @@ class NetworkManagerDeviceState: public QObject { namespace qs::dbus { template <> -struct DBusDataTransform { +struct DBusDataTransform { using Wire = quint32; - using Data = qs::service::networkmanager::NetworkManagerDeviceState::Enum; + using Data = qs::service::networkmanager::NMDeviceState::Enum; static DBusResult fromWire(Wire wire); }; template <> -struct DBusDataTransform { +struct DBusDataTransform { using Wire = quint32; - using Data = qs::service::networkmanager::NetworkManagerDeviceType::Enum; + using Data = qs::service::networkmanager::NMDeviceType::Enum; static DBusResult fromWire(Wire wire); }; @@ -104,19 +104,19 @@ struct DBusDataTransform bindableType() const { + [[nodiscard]] QBindable bindableType() const { return &this->bType; }; - [[nodiscard]] QBindable bindableState() const { + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; [[nodiscard]] QBindable bindableManaged() const { return &this->bManaged; }; @@ -141,19 +141,19 @@ class NetworkManagerDevice: public QObject { private: // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, NetworkManagerDeviceType::Enum, bType, &NetworkManagerDevice::typeChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, NetworkManagerDeviceState::Enum, bState, &NetworkManagerDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, bool, bManaged, &NetworkManagerDevice::managedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManagerDevice, QString, bInterface, &NetworkManagerDevice::interfaceChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManagerDevice, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pType, bType, deviceProperties, "DeviceType"); - QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pState, bState, deviceProperties, "State"); - QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pManaged, bManaged, deviceProperties, "Managed"); - QS_DBUS_PROPERTY_BINDING(NetworkManagerDevice, pInterface, bInterface, deviceProperties, "Interface"); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceType::Enum, bType, &NMDevice::typeChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDevice, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NMDevice, pType, bType, deviceProperties, "DeviceType"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pInterface, bInterface, deviceProperties, "Interface"); // clang-format on - DBusNetworkManagerDevice* device = nullptr; + DBusNMDevice* device = nullptr; }; } // namespace qs::service::networkmanager From 25042100012a5caaab1d02f779e7798d691d66d4 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 14:55:10 -0500 Subject: [PATCH 11/32] Revert "feat(nix): nm buildInput" This reverts commit 8679f79d32fa86dcc0547c19c6c3c3f5966a14fc. --- default.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/default.nix b/default.nix index 33250cc9..73cd8d16 100644 --- a/default.nix +++ b/default.nix @@ -20,7 +20,6 @@ libdrm, libgbm ? null, pipewire, - networkmanager, pam, gitRev ? (let @@ -41,7 +40,6 @@ withWayland ? true, withX11 ? true, withPipewire ? true, - withNetworkManager ? true, withPam ? true, withHyprland ? true, withI3 ? true, @@ -72,8 +70,7 @@ ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ] ++ lib.optional withX11 xorg.libxcb ++ lib.optional withPam pam - ++ lib.optional withPipewire pipewire - ++ lib.optional withNetworkManager networkmanager; + ++ lib.optional withPipewire pipewire; cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; @@ -87,7 +84,6 @@ (lib.cmakeBool "WAYLAND" withWayland) (lib.cmakeBool "SCREENCOPY" (libgbm != null)) (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire) - (lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager) (lib.cmakeBool "SERVICE_PAM" withPam) (lib.cmakeBool "HYPRLAND" withHyprland) (lib.cmakeBool "I3" withI3) From d3238817084017690113e31a2d3f5c78c5aca6bc Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 15:00:06 -0500 Subject: [PATCH 12/32] feat: AP, Wireless xml --- ....freedesktop.NetworkManager.AccessPoint.xml | 11 +++++++++++ ...edesktop.NetworkManager.Device.Wireless.xml | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml create mode 100644 src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml new file mode 100644 index 00000000..33872050 --- /dev/null +++ b/src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml new file mode 100644 index 00000000..17b712bb --- /dev/null +++ b/src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + arg name="access_ponit" type="o"/> + + + From 470579656aa17a75b2c5b2c2131275bd8be78eaf Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 15:04:30 -0500 Subject: [PATCH 13/32] fix test --- src/services/networkmanager/test/nm.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/networkmanager/test/nm.qml b/src/services/networkmanager/test/nm.qml index 91e83d07..5a66e94f 100644 --- a/src/services/networkmanager/test/nm.qml +++ b/src/services/networkmanager/test/nm.qml @@ -20,7 +20,7 @@ FloatingWindow { font.bold: true } - Label { text: "Current state: " + NMState.toString(NetworkManager.state) } + Label { text: "Current state: " + NetworkManagerState.toString(NetworkManager.state) } } ListView { @@ -40,8 +40,8 @@ FloatingWindow { text: `Device ${index}: ${modelData.interface}` font.bold: true } - Label { text: "Type: " + NMState.toString(modelData.type) } - Label { text: "State: " + NMState.toString(modelData.state) } + Label { text: "Type: " + NMDeviceType.toString(modelData.type) } + Label { text: "State: " + NMDeviceState.toString(modelData.state) } Label { text: `Managed: ${modelData.managed}` } } } From a1515c191d169a2166775cc6c83557383ed32fbb Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 9 Jul 2025 17:07:44 -0500 Subject: [PATCH 14/32] feat: begin wireless NMWireless will be a property of Device (if DeviceType==Wifi). In the future if many org.freedesktop.networkmanager.Device.* are implemented, we can use the factory pattern to create an NMDevice with its subdevice properties and methods. --- src/services/networkmanager/CMakeLists.txt | 11 ++++ src/services/networkmanager/core.hpp | 6 +- src/services/networkmanager/device.hpp | 9 +-- src/services/networkmanager/wireless.cpp | 39 +++++++++++ src/services/networkmanager/wireless.hpp | 77 ++++++++++++++++++++++ 5 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 src/services/networkmanager/wireless.cpp create mode 100644 src/services/networkmanager/wireless.hpp diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt index aa38a3b7..7947ab5d 100644 --- a/src/services/networkmanager/CMakeLists.txt +++ b/src/services/networkmanager/CMakeLists.txt @@ -18,9 +18,20 @@ qt_add_dbus_interface(DBUS_INTERFACES dbus_device ) +set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES + CLASSNAME DBusNMWireless + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.Wireless.xml + dbus_wireless +) + qt_add_library(quickshell-service-networkmanager STATIC core.cpp device.cpp + wireless.cpp ${DBUS_INTERFACES} ) diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp index c0fc4fc5..50f49a15 100644 --- a/src/services/networkmanager/core.hpp +++ b/src/services/networkmanager/core.hpp @@ -107,7 +107,7 @@ class NetworkManagerQml: public QObject { Q_OBJECT; QML_NAMED_ELEMENT(NetworkManager); QML_SINGLETON; - Q_PROPERTY(qs::service::networkmanager::NMDevice* wifiDevice READ wifiDevice); + Q_PROPERTY(qs::service::networkmanager::NMDevice* wifiDevice READ wifiDevice CONSTANT); QSDOC_TYPE_OVERRIDE(ObjectModel*); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); // clang-format off @@ -116,9 +116,7 @@ class NetworkManagerQml: public QObject { public: explicit NetworkManagerQml(QObject* parent = nullptr); - [[nodiscard]] static NMDevice* wifiDevice() { - return NetworkManager::instance()->wifiDevice(); - } + [[nodiscard]] static NMDevice* wifiDevice() { return NetworkManager::instance()->wifiDevice(); } [[nodiscard]] static ObjectModel* devices() { return NetworkManager::instance()->devices(); diff --git a/src/services/networkmanager/device.hpp b/src/services/networkmanager/device.hpp index aabeb321..99b473c4 100644 --- a/src/services/networkmanager/device.hpp +++ b/src/services/networkmanager/device.hpp @@ -117,19 +117,14 @@ class NMDevice: public QObject { public: explicit NMDevice(QObject* parent = nullptr); - void init(const QString& path); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; - [[nodiscard]] QBindable bindableType() const { - return &this->bType; - }; - [[nodiscard]] QBindable bindableState() const { - return &this->bState; - }; + [[nodiscard]] QBindable bindableType() const { return &this->bType; }; + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; [[nodiscard]] QBindable bindableManaged() const { return &this->bManaged; }; [[nodiscard]] QBindable bindableInterface() const { return &this->bInterface; }; diff --git a/src/services/networkmanager/wireless.cpp b/src/services/networkmanager/wireless.cpp new file mode 100644 index 00000000..a490035c --- /dev/null +++ b/src/services/networkmanager/wireless.cpp @@ -0,0 +1,39 @@ +#include "wireless.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_wireless.h" + +namespace qs::service::networkmanager { + +NMWireless::NMWireless(QObject* parent): QObject(parent) {} + +QString NMWirelessMode::toString(NMWirelessMode::Enum mode) { + switch (mode) { + case NMWirelessMode::Unknown: return "Unknown"; + case NMWirelessMode::Adhoc: return "Adhoc"; + case NMWirelessMode::Infra: return "Infra"; + case NMWirelessMode::AP: return "AP"; + case NMWirelessMode::Mesh: return "Mesh"; + default: return "Unknown"; + } +} + +} // namespace qs::service::networkmanager + +namespace qs::dbus { + +using namespace qs::service::networkmanager; + +DBusResult DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/services/networkmanager/wireless.hpp b/src/services/networkmanager/wireless.hpp new file mode 100644 index 00000000..4c875141 --- /dev/null +++ b/src/services/networkmanager/wireless.hpp @@ -0,0 +1,77 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_wireless.h" + +namespace qs::service::networkmanager { + +class NMWirelessMode: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Adhoc = 1, + Infra = 2, + AP = 3, + Mesh = 4, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NMWirelessMode::Enum mode); +}; + +} // namespace qs::service::networkmanager + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::service::networkmanager::NMWirelessMode::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::service::networkmanager { + +class NMWireless: public QObject { + Q_OBJECT + QML_ELEMENT; + QML_UNCREATABLE("NMWireless can only be acquired from NMDevice"); + //clang-format off + Q_PROPERTY(NMWirelessMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode); + Q_PROPERTY(quint32 bitrate READ default NOTIFY bitrateChanged BINDABLE bindableBitrate); + +public: + explicit NMWireless(QObject* parent = nullptr); + void init(const QString& path); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QBindable bindableMode() const { return &this->bMode; }; + [[nodiscard]] QBindable bindableBitrate() const { return &this->bBitrate; }; + +signals: + void modeChanged(); + void bitrateChanged(); + +private: + Q_OBJECT_BINDABLE_PROPERTY(NMWireless, NMWirelessMode::Enum, bMode, &NMWireless::modeChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWireless, quint32, bBitrate, &NMWireless::bitrateChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NMWireless, pMode, bMode, deviceProperties, "Mode"); + QS_DBUS_PROPERTY_BINDING(NMWireless, pBitrate, bBitrate, deviceProperties, "Bitrate"); + + DBusNMWireless* wireless = nullptr; +}; + +} // namespace qs::service::networkmanager From e445c9bd70b282e0649ff1e4a1f178c7d74cf72a Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 11 Jul 2025 03:03:38 -0500 Subject: [PATCH 15/32] Quickshell.Network --- CMakeLists.txt | 4 +- src/CMakeLists.txt | 4 + src/network/CMakeLists.txt | 54 ++++++ src/network/api.cpp | 38 +++++ src/network/api.hpp | 77 +++++++++ .../core.cpp => network/nm.cpp} | 116 +++++-------- src/network/nm.hpp | 48 ++++++ src/network/nmdevice.cpp | 41 +++++ src/network/nmdevice.hpp | 36 ++++ src/network/note | 12 ++ ...freedesktop.NetworkManager.AccessPoint.xml | 0 ...desktop.NetworkManager.Device.Wireless.xml | 0 .../org.freedesktop.NetworkManager.Device.xml | 8 + .../org.freedesktop.NetworkManager.xml | 0 .../test/nm.qml => network/test/network.qml} | 20 +-- src/services/CMakeLists.txt | 4 - src/services/networkmanager/CMakeLists.txt | 54 ------ src/services/networkmanager/core.hpp | 133 --------------- src/services/networkmanager/device.cpp | 86 ---------- src/services/networkmanager/device.hpp | 154 ------------------ .../org.freedesktop.NetworkManager.Device.xml | 16 -- src/services/networkmanager/wireless.cpp | 39 ----- src/services/networkmanager/wireless.hpp | 77 --------- 23 files changed, 366 insertions(+), 655 deletions(-) create mode 100644 src/network/CMakeLists.txt create mode 100644 src/network/api.cpp create mode 100644 src/network/api.hpp rename src/{services/networkmanager/core.cpp => network/nm.cpp} (58%) create mode 100644 src/network/nm.hpp create mode 100644 src/network/nmdevice.cpp create mode 100644 src/network/nmdevice.hpp create mode 100644 src/network/note rename src/{services/networkmanager => network}/org.freedesktop.NetworkManager.AccessPoint.xml (100%) rename src/{services/networkmanager => network}/org.freedesktop.NetworkManager.Device.Wireless.xml (100%) create mode 100644 src/network/org.freedesktop.NetworkManager.Device.xml rename src/{services/networkmanager => network}/org.freedesktop.NetworkManager.xml (100%) rename src/{services/networkmanager/test/nm.qml => network/test/network.qml} (50%) delete mode 100644 src/services/networkmanager/CMakeLists.txt delete mode 100644 src/services/networkmanager/core.hpp delete mode 100644 src/services/networkmanager/device.cpp delete mode 100644 src/services/networkmanager/device.hpp delete mode 100644 src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml delete mode 100644 src/services/networkmanager/wireless.cpp delete mode 100644 src/services/networkmanager/wireless.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c827c3c..d85a2db5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,6 @@ boption(X11 "X11" ON) boption(I3 "I3/Sway" ON) boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3) boption(SERVICE_STATUS_NOTIFIER "System Tray" ON) -boption(SERVICE_NETWORKMANAGER "NetworkManager" ON) boption(SERVICE_PIPEWIRE "PipeWire" ON) boption(SERVICE_MPRIS "Mpris" ON) boption(SERVICE_PAM "Pam" ON) @@ -72,6 +71,7 @@ boption(SERVICE_GREETD "Greetd" ON) boption(SERVICE_UPOWER "UPower" ON) boption(SERVICE_NOTIFICATIONS "Notifications" ON) boption(BLUETOOTH "Bluetooth" ON) +boption(NETWORK "Network" ON) include(cmake/install-qml-module.cmake) include(cmake/util.cmake) @@ -118,7 +118,7 @@ if (WAYLAND) list(APPEND QT_FPDEPS WaylandClient) endif() -if (SERVICE.NETWORKMANAGER OR SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH) +if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK) set(DBUS ON) endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 52db00a5..c95ecf71 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,3 +33,7 @@ add_subdirectory(services) if (BLUETOOTH) add_subdirectory(bluetooth) endif() + +if (NETWORK) + add_subdirectory(network) +endif() diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt new file mode 100644 index 00000000..62bcca2f --- /dev/null +++ b/src/network/CMakeLists.txt @@ -0,0 +1,54 @@ +set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES + CLASSNAME DBusNetworkManager + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.xml + dbus_nm_service +) + +set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES + CLASSNAME DBusNMDevice + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.xml + dbus_nm_device +) + +set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES + CLASSNAME DBusNMWireless + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.Wireless.xml + dbus_nm_wireless +) + +qt_add_library(quickshell-network STATIC + api.cpp + nm.cpp + nmdevice.cpp + ${DBUS_INTERFACES} +) + +# dbus headers +target_include_directories(quickshell-network PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + +qt_add_qml_module(quickshell-network + URI Quickshell.Network + VERSION 0.1 + DEPENDENCIES QtQml +) + +qs_add_module_deps_light(quickshell-network Quickshell) +install_qml_module(quickshell-network) + +target_link_libraries(quickshell-network PRIVATE Qt::Qml Qt::DBus) +qs_add_link_dependencies(quickshell-network quickshell-dbus) +target_link_libraries(quickshell PRIVATE quickshell-networkplugin) + +qs_module_pch(quickshell-network SET dbus) diff --git a/src/network/api.cpp b/src/network/api.cpp new file mode 100644 index 00000000..69caad51 --- /dev/null +++ b/src/network/api.cpp @@ -0,0 +1,38 @@ +#include "api.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../core/model.hpp" +#include "../dbus/bus.hpp" +#include "../dbus/properties.hpp" +#include "nm.hpp" + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNetwork, "quickshell.service.network", QtWarningMsg); +} + +Device::Device(QObject* parent): QObject(parent) {}; + +Network::Network(QObject* parent): QObject(parent) { + // Try each backend + + // NetworkManager + auto* nm = new NetworkManager(); + if (nm->isAvailable()) { + this->backend = nm; + } else { + // None found + this->backend = nullptr; + qCDebug(logNetwork) << "Network will not work. Could not find an available backend."; + } +} + +} // namespace qs::network diff --git a/src/network/api.hpp b/src/network/api.hpp new file mode 100644 index 00000000..ac52e566 --- /dev/null +++ b/src/network/api.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../core/model.hpp" +#include "../dbus/properties.hpp" + +namespace qs::network { + +class Device: public QObject { + Q_OBJECT; + Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); + Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); + Q_PROPERTY(bool powered READ default NOTIFY poweredChanged BINDABLE bindablePowered); + + QML_ELEMENT; + QML_UNCREATABLE("Devices can only be acquired through Network"); + +public: + [[nodiscard]] QBindable bindableName() const { return &this->bName; }; + [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; + [[nodiscard]] QBindable bindablePowered() const { return &this->bPowered; }; + +signals: + void nameChanged(); + void addressChanged(); + void poweredChanged(); + +protected: + explicit Device(QObject* parent = nullptr); + Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bName, &Device::nameChanged); + Q_OBJECT_BINDABLE_PROPERTY(Device, bool, bPowered, &Device::poweredChanged); + Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bAddress, &Device::addressChanged); + QS_DBUS_BINDABLE_PROPERTY_GROUP(Device, deviceProperties); +}; + +// class WirelessDevice; + +class NetworkBackend: public QObject { + Q_OBJECT; + +public: + [[nodiscard]] virtual bool isAvailable() const = 0; + virtual Device* wireless() = 0; + virtual UntypedObjectModel* allDevices() = 0; + +protected: + explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; +}; + +class Network: public QObject { + Q_OBJECT; + QML_NAMED_ELEMENT(Network); + QML_SINGLETON; + + Q_PROPERTY(Device* wireless READ wireless CONSTANT); + Q_PROPERTY(UntypedObjectModel* allDevices READ allDevices CONSTANT); + +public: + explicit Network(QObject* parent = nullptr); + [[nodiscard]] Device* wireless() { return backend ? backend->wireless() : nullptr; } + [[nodiscard]] UntypedObjectModel* allDevices() { return backend ? backend->allDevices() : nullptr; } + +private: + void findBackend(); + class NetworkBackend* backend = nullptr; +}; + +} // namespace qs::network diff --git a/src/services/networkmanager/core.cpp b/src/network/nm.cpp similarity index 58% rename from src/services/networkmanager/core.cpp rename to src/network/nm.cpp index 2b5a86fc..1530bc9f 100644 --- a/src/services/networkmanager/core.cpp +++ b/src/network/nm.cpp @@ -1,46 +1,40 @@ -#include "core.hpp" +#include "nm.hpp" -#include -#include +#include #include -#include -#include #include -#include +#include +#include +#include +#include +#include +#include -#include "../../core/model.hpp" -#include "../../dbus/bus.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_service.h" +#include "../dbus/bus.hpp" +#include "../dbus/properties.hpp" +#include "api.hpp" +#include "dbus_nm_service.h" -namespace qs::service::networkmanager { - -const QString NM_SERVICE = "org.freedesktop.NetworkManager"; -const QString NM_PATH = "/org/freedesktop/NetworkManager"; +namespace qs::network { namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.service.networkmanager", QtWarningMsg); } -QString NetworkManagerState::toString(NetworkManagerState::Enum state) { - auto metaEnum = QMetaEnum::fromType(); - if (metaEnum.valueToKey(state)) { - return QString(metaEnum.valueToKey(state)); - } - return "Invalid state"; -} +const QString NM_SERVICE = "org.freedesktop.NetworkManager"; +const QString NM_PATH = "/org/freedesktop/NetworkManager"; -NetworkManager::NetworkManager() { - qCDebug(logNetworkManager) << "Starting NetworkManager Service"; +NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { + qCDebug(logNetworkManager) << "Starting NetworkManager Network Backend"; auto bus = QDBusConnection::systemBus(); if (!bus.isConnected()) { qCWarning(logNetworkManager - ) << "Could not connect to DBus. NetworkManager service will not work."; + ) << "Could not connect to DBus. NetworkManager backend will not work."; return; } - this->service = new DBusNetworkManagerService(NM_SERVICE, NM_PATH, bus, this); + this->service = new DBusNetworkManager(NM_SERVICE, NM_PATH, bus, this); if (!this->service->isValid()) { qCDebug(logNetworkManager @@ -48,11 +42,11 @@ NetworkManager::NetworkManager() { dbus::tryLaunchService(this, bus, NM_SERVICE, [this](bool success) { if (success) { - qCDebug(logNetworkManager) << "Successfully launched NetworkManager service."; + qCDebug(logNetworkManager) << "Successfully launched NetworkManager backend."; this->init(); } else { qCWarning(logNetworkManager) - << "Could not start NetworkManager. The NetworkManager service will not work."; + << "Could not start NetworkManager. The NetworkManager backend will not work."; } }); } else { @@ -63,14 +57,14 @@ NetworkManager::NetworkManager() { void NetworkManager::init() { QObject::connect( this->service, - &DBusNetworkManagerService::DeviceAdded, + &DBusNetworkManager::DeviceAdded, this, &NetworkManager::onDeviceAdded ); QObject::connect( this->service, - &DBusNetworkManagerService::DeviceRemoved, + &DBusNetworkManager::DeviceRemoved, this, &NetworkManager::onDeviceRemoved ); @@ -82,7 +76,7 @@ void NetworkManager::init() { } void NetworkManager::registerDevices() { - auto pending = this->service->GetDevices(); + auto pending = this->service->GetAllDevices(); auto* call = new QDBusPendingCallWatcher(pending, this); auto responseCallback = [this](QDBusPendingCallWatcher* call) { @@ -102,24 +96,6 @@ void NetworkManager::registerDevices() { QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } -void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { - this->registerDevice(path.path()); -} - -void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { - auto iter = this->mDeviceHash.find(path.path()); - - if (iter == this->mDeviceHash.end()) { - qCWarning(logNetworkManager) << "NetworkManager service sent removal signal for" << path.path() - << "which is not registered."; - } else { - auto* device = iter.value(); - this->mDeviceHash.erase(iter); - this->mDevices.removeObject(device); - qCDebug(logNetworkManager) << "NMDevice" << device->path() << "removed."; - } -} - void NetworkManager::registerDevice(const QString& path) { if (this->mDeviceHash.contains(path)) { qCDebug(logNetworkManager) << "Skipping duplicate registration of NMDevice" << path; @@ -134,40 +110,32 @@ void NetworkManager::registerDevice(const QString& path) { delete device; return; } - if (device->bindableType().value() == NMDeviceType::Wifi) { - mWifiDevice = device; - } this->mDeviceHash.insert(path, device); this->mDevices.insertObject(device); qCDebug(logNetworkManager) << "Registered NMDevice" << path; } -ObjectModel* NetworkManager::devices() { return &this->mDevices; } -NMDevice* NetworkManager::wifiDevice() { return this->mWifiDevice; } - -NetworkManager* NetworkManager::instance() { - static NetworkManager* instance = new NetworkManager(); // NOLINT - return instance; -} - -NetworkManagerQml::NetworkManagerQml(QObject* parent): QObject(parent) { - QObject::connect( - NetworkManager::instance(), - &NetworkManager::stateChanged, - this, - &NetworkManagerQml::stateChanged - ); +void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { + this->registerDevice(path.path()); } -} // namespace qs::service::networkmanager - -namespace qs::dbus { -using namespace qs::service::networkmanager; +void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { + auto iter = this->mDeviceHash.find(path.path()); -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); + if (iter == this->mDeviceHash.end()) { + qCWarning(logNetworkManager) << "NetworkManager backend sent removal signal for" << path.path() + << "which is not registered."; + } else { + auto* device = iter.value(); + this->mDeviceHash.erase(iter); + this->mDevices.removeObject(device); + qCDebug(logNetworkManager) << "NMDevice" << device->path() << "removed."; + } } -} // namespace qs::dbus +UntypedObjectModel* NetworkManager::allDevices() { return &this->mDevices; } +NMDevice* NetworkManager::wireless() { return &this->mWireless; } +bool NetworkManager::isAvailable() const { return this->service && this->service->isValid(); } + +} // namespace qs::network diff --git a/src/network/nm.hpp b/src/network/nm.hpp new file mode 100644 index 00000000..33177a17 --- /dev/null +++ b/src/network/nm.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "api.hpp" +#include "dbus_nm_service.h" +#include "nmdevice.hpp" + +namespace qs::network { + +class NetworkManager: public NetworkBackend { + Q_OBJECT; + +public: + explicit NetworkManager(QObject* parent = nullptr); + + UntypedObjectModel* allDevices() override; + NMDevice* wireless() override; + [[nodiscard]] bool isAvailable() const override; + +private slots: + void onDeviceAdded(const QDBusObjectPath& path); + void onDeviceRemoved(const QDBusObjectPath& path); + +private: + void init(); + void registerDevice(const QString& path); + void registerDevices(); + + QHash mDeviceHash; + ObjectModel mDevices {this}; + NMDevice mWireless; + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, serviceProperties); + + DBusNetworkManager* service = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/nmdevice.cpp b/src/network/nmdevice.cpp new file mode 100644 index 00000000..7baa89fd --- /dev/null +++ b/src/network/nmdevice.cpp @@ -0,0 +1,41 @@ +#include "nmdevice.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "dbus_nm_device.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNMDevice, "quickshell.service.networkmanager.device", QtWarningMsg); +} + +NMDevice::NMDevice(QObject* parent): Device(parent) {} + +void NMDevice::init(const QString& path) { + this->device = + new DBusNMDevice("org.freedesktop.NetworkManager", path, QDBusConnection::systemBus(), this); + + if (!this->device->isValid()) { + qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; + return; + } + + this->deviceProperties.setInterface(this->device); + this->deviceProperties.updateAllViaGetAll(); +} + +bool NMDevice::isValid() const { return this->device && this->device->isValid(); } +QString NMDevice::address() const { return this->device ? this->device->service() : QString(); } +QString NMDevice::path() const { return this->device ? this->device->path() : QString(); } + +} // namespace qs::network diff --git a/src/network/nmdevice.hpp b/src/network/nmdevice.hpp new file mode 100644 index 00000000..fbd75476 --- /dev/null +++ b/src/network/nmdevice.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "api.hpp" +#include "dbus_nm_device.h" + +namespace qs::network { + +class NMDevice: public Device { + Q_OBJECT; + +public: + explicit NMDevice(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + +private: + QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bName, deviceProperties, "Interface"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bAddress, deviceProperties, "HwAddress"); + + DBusNMDevice* device = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/note b/src/network/note new file mode 100644 index 00000000..ef97d8b7 --- /dev/null +++ b/src/network/note @@ -0,0 +1,12 @@ +Network + - Device(s) + - Name + - Address + - Powered (True/False) + - Wireless: Track the default/current wireless station + - Extends Device + - Disconnect + - Active AccessPoint + - AccessPoints + - Scan + - State Connected,Disconnected,Connecting,Disconnecting,Unknown diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/org.freedesktop.NetworkManager.AccessPoint.xml similarity index 100% rename from src/services/networkmanager/org.freedesktop.NetworkManager.AccessPoint.xml rename to src/network/org.freedesktop.NetworkManager.AccessPoint.xml diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml similarity index 100% rename from src/services/networkmanager/org.freedesktop.NetworkManager.Device.Wireless.xml rename to src/network/org.freedesktop.NetworkManager.Device.Wireless.xml diff --git a/src/network/org.freedesktop.NetworkManager.Device.xml b/src/network/org.freedesktop.NetworkManager.Device.xml new file mode 100644 index 00000000..9a2b6914 --- /dev/null +++ b/src/network/org.freedesktop.NetworkManager.Device.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.xml b/src/network/org.freedesktop.NetworkManager.xml similarity index 100% rename from src/services/networkmanager/org.freedesktop.NetworkManager.xml rename to src/network/org.freedesktop.NetworkManager.xml diff --git a/src/services/networkmanager/test/nm.qml b/src/network/test/network.qml similarity index 50% rename from src/services/networkmanager/test/nm.qml rename to src/network/test/network.qml index 5a66e94f..139ff9f9 100644 --- a/src/services/networkmanager/test/nm.qml +++ b/src/network/test/network.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Widgets -import Quickshell.Services.NetworkManager +import Quickshell.Network FloatingWindow { color: contentItem.palette.window @@ -13,20 +13,10 @@ FloatingWindow { anchors.margins: 10 spacing: 10 - - ColumnLayout { - Label { - text: "NetworkManager Service Test" - font.bold: true - } - - Label { text: "Current state: " + NetworkManagerState.toString(NetworkManager.state) } - } - ListView { Layout.fillWidth: true Layout.fillHeight: true - model: NetworkManager.devices + model: Network.allDevices delegate: WrapperRectangle { width: parent.width @@ -37,12 +27,10 @@ FloatingWindow { ColumnLayout { Label { - text: `Device ${index}: ${modelData.interface}` + text: `Device ${index}: ${modelData.name}` font.bold: true } - Label { text: "Type: " + NMDeviceType.toString(modelData.type) } - Label { text: "State: " + NMDeviceState.toString(modelData.state) } - Label { text: `Managed: ${modelData.managed}` } + Label { text: "Hardware Address: " + modelData.address } } } } diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index 13198bb7..5ab5c550 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -25,7 +25,3 @@ endif() if (SERVICE_NOTIFICATIONS) add_subdirectory(notifications) endif() - -if (SERVICE_NETWORKMANAGER) - add_subdirectory(networkmanager) -endif() diff --git a/src/services/networkmanager/CMakeLists.txt b/src/services/networkmanager/CMakeLists.txt deleted file mode 100644 index 7947ab5d..00000000 --- a/src/services/networkmanager/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES - CLASSNAME DBusNetworkManagerService - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(DBUS_INTERFACES - org.freedesktop.NetworkManager.xml - dbus_service -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNMDevice - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.xml - dbus_device -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES - CLASSNAME DBusNMWireless - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.Wireless.xml - dbus_wireless -) - -qt_add_library(quickshell-service-networkmanager STATIC - core.cpp - device.cpp - wireless.cpp - ${DBUS_INTERFACES} -) - -# dbus headers -target_include_directories(quickshell-service-networkmanager PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) - -qt_add_qml_module(quickshell-service-networkmanager - URI Quickshell.Services.NetworkManager - VERSION 0.1 - DEPENDENCIES QtQml -) - -qs_add_module_deps_light(quickshell-service-networkmanager Quickshell) -install_qml_module(quickshell-service-networkmanager) - -target_link_libraries(quickshell-service-networkmanager PRIVATE Qt::Qml Qt::DBus) -qs_add_link_dependencies(quickshell-service-networkmanager quickshell-dbus) -target_link_libraries(quickshell PRIVATE quickshell-service-networkmanagerplugin) - -qs_module_pch(quickshell-service-networkmanager SET dbus) diff --git a/src/services/networkmanager/core.hpp b/src/services/networkmanager/core.hpp deleted file mode 100644 index 50f49a15..00000000 --- a/src/services/networkmanager/core.hpp +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/model.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_service.h" -#include "device.hpp" - -namespace qs::service::networkmanager { - -class NetworkManagerState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Asleep = 10, - Disconnected = 20, - Disconnecting = 30, - Connecting = 40, - ConnectedLocal = 50, - ConnectedSite = 60, - ConnectedGlobal = 70, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(qs::service::networkmanager::NetworkManagerState::Enum state); -}; - -} // namespace qs::service::networkmanager - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::service::networkmanager::NetworkManagerState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::service::networkmanager { - -class NetworkManager: public QObject { - Q_OBJECT; - -public: - [[nodiscard]] NMDevice* wifiDevice(); - [[nodiscard]] ObjectModel* devices(); - [[nodiscard]] QBindable bindableState() const { - return &this->bState; - }; - - static NetworkManager* instance(); - -signals: - void stateChanged(); - -private slots: - void onDeviceAdded(const QDBusObjectPath& path); - void onDeviceRemoved(const QDBusObjectPath& path); - -private: - explicit NetworkManager(); - - void init(); - void registerDevice(const QString& path); - void createDevice(const QString& path); - void registerDevices(); - - QHash mDeviceHash; - ObjectModel mDevices {this}; - NMDevice* mWifiDevice = nullptr; - - Q_OBJECT_BINDABLE_PROPERTY( - NetworkManager, - NetworkManagerState::Enum, - bState, - &NetworkManager::stateChanged - ); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, serviceProperties); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pState, bState, serviceProperties, "State"); - - DBusNetworkManagerService* service = nullptr; -}; - -///! Provides access to the NetworkManager service. -/// An interface to the [NetworkManager daemon], which can be used to -/// view and configure network interfaces and connections. -/// -/// > [!NOTE] The NetworkManager daemon must be installed to use this service. -/// -/// [NetworkManager daemon]: https://networkmanager.dev -class NetworkManagerQml: public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(NetworkManager); - QML_SINGLETON; - Q_PROPERTY(qs::service::networkmanager::NMDevice* wifiDevice READ wifiDevice CONSTANT); - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); - // clang-format off - Q_PROPERTY(NetworkManagerState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - // clang-format on - -public: - explicit NetworkManagerQml(QObject* parent = nullptr); - [[nodiscard]] static NMDevice* wifiDevice() { return NetworkManager::instance()->wifiDevice(); } - - [[nodiscard]] static ObjectModel* devices() { - return NetworkManager::instance()->devices(); - } - - [[nodiscard]] static QBindable bindableState() { - return NetworkManager::instance()->bindableState(); - } - -signals: - void stateChanged(); -}; - -} // namespace qs::service::networkmanager diff --git a/src/services/networkmanager/device.cpp b/src/services/networkmanager/device.cpp deleted file mode 100644 index 2b9a9ae4..00000000 --- a/src/services/networkmanager/device.cpp +++ /dev/null @@ -1,86 +0,0 @@ - -#include "device.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_device.h" - -using namespace qs::dbus; - -namespace qs::service::networkmanager { - -namespace { -Q_LOGGING_CATEGORY( - logNMDevice, - "quickshell.service.networkmanager.device", - QtWarningMsg -); -} - -QString NMDeviceState::toString(NMDeviceState::Enum state) { - auto metaEnum = QMetaEnum::fromType(); - if (metaEnum.valueToKey(state)) { - return QString(metaEnum.valueToKey(state)); - } - return "Invalid state"; -} - -QString NMDeviceType::toString(NMDeviceType::Enum type) { - auto metaEnum = QMetaEnum::fromType(); - if (metaEnum.valueToKey(type)) { - return QString(metaEnum.valueToKey(type)); - } - return "Invalid type"; -} - -NMDevice::NMDevice(QObject* parent): QObject(parent) {} - -void NMDevice::init(const QString& path) { - this->device = new DBusNMDevice( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->device->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; - return; - } - - this->deviceProperties.setInterface(this->device); - this->deviceProperties.updateAllViaGetAll(); -} - -bool NMDevice::isValid() const { return this->device && this->device->isValid(); } -QString NMDevice::address() const { - return this->device ? this->device->service() : QString(); -} -QString NMDevice::path() const { - return this->device ? this->device->path() : QString(); -} - -} // namespace qs::service::networkmanager - -namespace qs::dbus { - -using namespace qs::service::networkmanager; - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/services/networkmanager/device.hpp b/src/services/networkmanager/device.hpp deleted file mode 100644 index 99b473c4..00000000 --- a/src/services/networkmanager/device.hpp +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_device.h" - -namespace qs::service::networkmanager { - -class NMDeviceType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Generic = 14, - Ethernet = 1, - Wifi = 2, - Unused1 = 3, - Unused2 = 4, - Bluetooth = 5, - OlpcMesh = 6, - Wimax = 7, - Modem = 8, - Infiniband = 9, - Bond = 10, - Vlan = 11, - Adsl = 12, - Bridge = 13, - Team = 15, - Tun = 16, - Tunnel = 17, - Macvlan = 18, - Vxlan = 19, - Veth = 20, - Macsec = 21, - Dummy = 22, - Ppp = 23, - OvsInterface = 24, - OvsPort = 25, - OvsBridge = 26, - Wpan = 27, - Lowpan = 28, - WireGuard = 29, - WifiP2P = 30, - Vrf = 31, - Loopback = 32, - Hsr = 33, - Ipvlan = 34, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMDeviceType::Enum type); -}; - -class NMDeviceState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMDeviceState::Enum state); -}; - -} // namespace qs::service::networkmanager - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::service::networkmanager::NMDeviceState::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::service::networkmanager::NMDeviceType::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::service::networkmanager { - -class NMDevice: public QObject { - Q_OBJECT; - // clang-format off - Q_PROPERTY(NMDeviceType::Enum type READ default NOTIFY typeChanged BINDABLE bindableType); - Q_PROPERTY(NMDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - Q_PROPERTY(QString interface READ default NOTIFY interfaceChanged BINDABLE bindableInterface); - Q_PROPERTY(bool managed READ default NOTIFY managedChanged BINDABLE bindableManaged); - // clang-format on - QML_ELEMENT; - QML_UNCREATABLE("NMDevices can only be acquired from NetworkManager"); - -public: - explicit NMDevice(QObject* parent = nullptr); - void init(const QString& path); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - - [[nodiscard]] QBindable bindableType() const { return &this->bType; }; - [[nodiscard]] QBindable bindableState() const { return &this->bState; }; - [[nodiscard]] QBindable bindableManaged() const { return &this->bManaged; }; - [[nodiscard]] QBindable bindableInterface() const { return &this->bInterface; }; - -signals: - void typeChanged(); - void stateChanged(); - void interfaceChanged(); - void managedChanged(); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceType::Enum, bType, &NMDevice::typeChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDevice, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMDevice, pType, bType, deviceProperties, "DeviceType"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pInterface, bInterface, deviceProperties, "Interface"); - // clang-format on - - DBusNMDevice* device = nullptr; -}; - -} // namespace qs::service::networkmanager diff --git a/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml b/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml deleted file mode 100644 index dbd3be2c..00000000 --- a/src/services/networkmanager/org.freedesktop.NetworkManager.Device.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/src/services/networkmanager/wireless.cpp b/src/services/networkmanager/wireless.cpp deleted file mode 100644 index a490035c..00000000 --- a/src/services/networkmanager/wireless.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "wireless.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_wireless.h" - -namespace qs::service::networkmanager { - -NMWireless::NMWireless(QObject* parent): QObject(parent) {} - -QString NMWirelessMode::toString(NMWirelessMode::Enum mode) { - switch (mode) { - case NMWirelessMode::Unknown: return "Unknown"; - case NMWirelessMode::Adhoc: return "Adhoc"; - case NMWirelessMode::Infra: return "Infra"; - case NMWirelessMode::AP: return "AP"; - case NMWirelessMode::Mesh: return "Mesh"; - default: return "Unknown"; - } -} - -} // namespace qs::service::networkmanager - -namespace qs::dbus { - -using namespace qs::service::networkmanager; - -DBusResult DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/services/networkmanager/wireless.hpp b/src/services/networkmanager/wireless.hpp deleted file mode 100644 index 4c875141..00000000 --- a/src/services/networkmanager/wireless.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_wireless.h" - -namespace qs::service::networkmanager { - -class NMWirelessMode: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Adhoc = 1, - Infra = 2, - AP = 3, - Mesh = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMWirelessMode::Enum mode); -}; - -} // namespace qs::service::networkmanager - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::service::networkmanager::NMWirelessMode::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::service::networkmanager { - -class NMWireless: public QObject { - Q_OBJECT - QML_ELEMENT; - QML_UNCREATABLE("NMWireless can only be acquired from NMDevice"); - //clang-format off - Q_PROPERTY(NMWirelessMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode); - Q_PROPERTY(quint32 bitrate READ default NOTIFY bitrateChanged BINDABLE bindableBitrate); - -public: - explicit NMWireless(QObject* parent = nullptr); - void init(const QString& path); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QBindable bindableMode() const { return &this->bMode; }; - [[nodiscard]] QBindable bindableBitrate() const { return &this->bBitrate; }; - -signals: - void modeChanged(); - void bitrateChanged(); - -private: - Q_OBJECT_BINDABLE_PROPERTY(NMWireless, NMWirelessMode::Enum, bMode, &NMWireless::modeChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWireless, quint32, bBitrate, &NMWireless::bitrateChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMWireless, pMode, bMode, deviceProperties, "Mode"); - QS_DBUS_PROPERTY_BINDING(NMWireless, pBitrate, bBitrate, deviceProperties, "Bitrate"); - - DBusNMWireless* wireless = nullptr; -}; - -} // namespace qs::service::networkmanager From c4d8fc2743a8657f99cf785d4989101a3d62623a Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 11 Jul 2025 03:40:47 -0500 Subject: [PATCH 16/32] clean --- src/network/note | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/network/note diff --git a/src/network/note b/src/network/note deleted file mode 100644 index ef97d8b7..00000000 --- a/src/network/note +++ /dev/null @@ -1,12 +0,0 @@ -Network - - Device(s) - - Name - - Address - - Powered (True/False) - - Wireless: Track the default/current wireless station - - Extends Device - - Disconnect - - Active AccessPoint - - AccessPoints - - Scan - - State Connected,Disconnected,Connecting,Disconnecting,Unknown From 28bf2d756b0725c13cf3c050c34c7f838071b95d Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 11 Jul 2025 12:54:38 -0500 Subject: [PATCH 17/32] refactor: nm dir --- src/network/CMakeLists.txt | 19 ++++++----- src/network/api.cpp | 5 +-- src/network/nm/CMakeLists.txt | 32 +++++++++++++++++++ src/network/{nm.cpp => nm/backend.cpp} | 10 +++--- src/network/{nm.hpp => nm/backend.hpp} | 8 ++--- src/network/{nmdevice.cpp => nm/device.cpp} | 2 +- src/network/{nmdevice.hpp => nm/device.hpp} | 4 +-- ...freedesktop.NetworkManager.AccessPoint.xml | 11 +++++++ ...desktop.NetworkManager.Device.Wireless.xml | 18 +++++++++++ .../org.freedesktop.NetworkManager.Device.xml | 8 +++++ .../nm/org.freedesktop.NetworkManager.xml | 19 +++++++++++ 11 files changed, 112 insertions(+), 24 deletions(-) create mode 100644 src/network/nm/CMakeLists.txt rename src/network/{nm.cpp => nm/backend.cpp} (96%) rename src/network/{nm.hpp => nm/backend.hpp} (89%) rename src/network/{nmdevice.cpp => nm/device.cpp} (97%) rename src/network/{nmdevice.hpp => nm/device.hpp} (93%) create mode 100644 src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml create mode 100644 src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml create mode 100644 src/network/nm/org.freedesktop.NetworkManager.Device.xml create mode 100644 src/network/nm/org.freedesktop.NetworkManager.xml diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 62bcca2f..c5b556a3 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,11 +1,12 @@ +# NetworkManager DBus set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES CLASSNAME DBusNetworkManager NO_NAMESPACE TRUE ) -qt_add_dbus_interface(DBUS_INTERFACES +qt_add_dbus_interface(NM_DBUS_INTERFACES org.freedesktop.NetworkManager.xml - dbus_nm_service + dbus_nm_backend ) set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES @@ -13,7 +14,7 @@ set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES NO_NAMESPACE TRUE ) -qt_add_dbus_interface(DBUS_INTERFACES +qt_add_dbus_interface(NM_DBUS_INTERFACES org.freedesktop.NetworkManager.Device.xml dbus_nm_device ) @@ -23,20 +24,22 @@ set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml P NO_NAMESPACE TRUE ) -qt_add_dbus_interface(DBUS_INTERFACES +qt_add_dbus_interface(NM_DBUS_INTERFACES org.freedesktop.NetworkManager.Device.Wireless.xml dbus_nm_wireless ) qt_add_library(quickshell-network STATIC api.cpp - nm.cpp - nmdevice.cpp - ${DBUS_INTERFACES} + nm/backend.cpp + nm/device.cpp + ${NM_DBUS_INTERFACES} ) # dbus headers -target_include_directories(quickshell-network PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(quickshell-network PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} +) qt_add_qml_module(quickshell-network URI Quickshell.Network diff --git a/src/network/api.cpp b/src/network/api.cpp index 69caad51..3ca36a1a 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -8,10 +8,7 @@ #include #include -#include "../core/model.hpp" -#include "../dbus/bus.hpp" -#include "../dbus/properties.hpp" -#include "nm.hpp" +#include "nm/backend.hpp" namespace qs::network { diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt new file mode 100644 index 00000000..0ad7bdcc --- /dev/null +++ b/src/network/nm/CMakeLists.txt @@ -0,0 +1,32 @@ +set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES + CLASSNAME DBusNetworkManager + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + org.freedesktop.NetworkManager.xml + dbus_nm_backend +) + +set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES + CLASSNAME DBusNMDevice + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.xml + dbus_nm_device +) + +set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES + CLASSNAME DBusNMWireless + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.Wireless.xml + dbus_nm_wireless +) + +set(NM_DBUS_INTERFACES ${NM_DBUS_INTERFACES} PARENT_SCOPE) +set(NM_DBUS_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) diff --git a/src/network/nm.cpp b/src/network/nm/backend.cpp similarity index 96% rename from src/network/nm.cpp rename to src/network/nm/backend.cpp index 1530bc9f..d6034b48 100644 --- a/src/network/nm.cpp +++ b/src/network/nm/backend.cpp @@ -1,4 +1,4 @@ -#include "nm.hpp" +#include "backend.hpp" #include #include @@ -10,10 +10,10 @@ #include #include -#include "../dbus/bus.hpp" -#include "../dbus/properties.hpp" -#include "api.hpp" -#include "dbus_nm_service.h" +#include "../../dbus/bus.hpp" +#include "../../dbus/properties.hpp" +#include "../api.hpp" +#include "dbus_nm_backend.h" namespace qs::network { diff --git a/src/network/nm.hpp b/src/network/nm/backend.hpp similarity index 89% rename from src/network/nm.hpp rename to src/network/nm/backend.hpp index 33177a17..b875b68d 100644 --- a/src/network/nm.hpp +++ b/src/network/nm/backend.hpp @@ -10,10 +10,10 @@ #include #include -#include "../dbus/properties.hpp" -#include "api.hpp" -#include "dbus_nm_service.h" -#include "nmdevice.hpp" +#include "../../dbus/properties.hpp" +#include "../api.hpp" +#include "dbus_nm_backend.h" +#include "device.hpp" namespace qs::network { diff --git a/src/network/nmdevice.cpp b/src/network/nm/device.cpp similarity index 97% rename from src/network/nmdevice.cpp rename to src/network/nm/device.cpp index 7baa89fd..67f762a8 100644 --- a/src/network/nmdevice.cpp +++ b/src/network/nm/device.cpp @@ -1,4 +1,4 @@ -#include "nmdevice.hpp" +#include "device.hpp" #include #include diff --git a/src/network/nmdevice.hpp b/src/network/nm/device.hpp similarity index 93% rename from src/network/nmdevice.hpp rename to src/network/nm/device.hpp index fbd75476..0c7e8a39 100644 --- a/src/network/nmdevice.hpp +++ b/src/network/nm/device.hpp @@ -10,8 +10,8 @@ #include #include -#include "../dbus/properties.hpp" -#include "api.hpp" +#include "../../dbus/properties.hpp" +#include "../api.hpp" #include "dbus_nm_device.h" namespace qs::network { diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml new file mode 100644 index 00000000..33872050 --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml new file mode 100644 index 00000000..17b712bb --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + arg name="access_ponit" type="o"/> + + + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml new file mode 100644 index 00000000..9a2b6914 --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.Device.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/network/nm/org.freedesktop.NetworkManager.xml b/src/network/nm/org.freedesktop.NetworkManager.xml new file mode 100644 index 00000000..4f9ca03e --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + From 1d02e1cc462d3c0e7077c40cb28a71a9dfb216bb Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 11 Jul 2025 13:02:16 -0500 Subject: [PATCH 18/32] fix: log names --- src/network/api.cpp | 11 ++++++----- src/network/nm/device.cpp | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 3ca36a1a..9771f28d 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -13,7 +13,7 @@ namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNetwork, "quickshell.service.network", QtWarningMsg); +Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } Device::Device(QObject* parent): QObject(parent) {}; @@ -25,11 +25,12 @@ Network::Network(QObject* parent): QObject(parent) { auto* nm = new NetworkManager(); if (nm->isAvailable()) { this->backend = nm; - } else { - // None found - this->backend = nullptr; - qCDebug(logNetwork) << "Network will not work. Could not find an available backend."; + return; } + + // None found + this->backend = nullptr; + qCDebug(logNetwork) << "Network will not work. Could not find an available backend."; } } // namespace qs::network diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index 67f762a8..89e31192 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -16,7 +16,7 @@ using namespace qs::dbus; namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNMDevice, "quickshell.service.networkmanager.device", QtWarningMsg); +Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); } NMDevice::NMDevice(QObject* parent): Device(parent) {} From 112eb66b83eb6aebc01366efad0f4b9ec082a851 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Fri, 11 Jul 2025 16:24:49 -0500 Subject: [PATCH 19/32] feat: Wireless --- src/network/api.hpp | 79 +++++++++++++++---- src/network/nm/backend.cpp | 22 +++--- src/network/nm/backend.hpp | 13 +-- src/network/nm/device.cpp | 18 +++++ src/network/nm/device.hpp | 12 +++ .../org.freedesktop.NetworkManager.Device.xml | 3 +- src/network/nm/wireless.cpp | 41 ++++++++++ src/network/nm/wireless.hpp | 35 ++++++++ src/network/test/network.qml | 2 +- 9 files changed, 193 insertions(+), 32 deletions(-) create mode 100644 src/network/nm/wireless.cpp create mode 100644 src/network/nm/wireless.hpp diff --git a/src/network/api.hpp b/src/network/api.hpp index ac52e566..a5dfbd21 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -15,42 +15,93 @@ namespace qs::network { +// -- Device -- + class Device: public QObject { Q_OBJECT; - Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); - Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); - Q_PROPERTY(bool powered READ default NOTIFY poweredChanged BINDABLE bindablePowered); - QML_ELEMENT; QML_UNCREATABLE("Devices can only be acquired through Network"); + // clang-format off + Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); + Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); + // clang-format on + public: [[nodiscard]] QBindable bindableName() const { return &this->bName; }; [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; - [[nodiscard]] QBindable bindablePowered() const { return &this->bPowered; }; signals: void nameChanged(); void addressChanged(); - void poweredChanged(); protected: explicit Device(QObject* parent = nullptr); Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bName, &Device::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(Device, bool, bPowered, &Device::poweredChanged); Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bAddress, &Device::addressChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(Device, deviceProperties); }; -// class WirelessDevice; +// -- Wireless Device -- + +class WirelessState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Disconnected = 10, + Connecting = 20, + Connected = 30, + Disconnecting = 40, + }; + Q_ENUM(Enum); +}; + +class Wireless: public QObject { + Q_OBJECT; + // clang-format off + Q_PROPERTY(bool powered READ powered WRITE setPowered NOTIFY poweredChanged); + Q_PROPERTY(WirelessState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + //clang-format on + +public: + Q_INVOKABLE virtual void disconnect() = 0; + Q_INVOKABLE virtual void scan() = 0; + + [[nodiscard]] bool powered() const { return this->bPowered; }; + virtual void setPowered(bool powered) = 0; + + [[nodiscard]] QBindable bindablePowered() { return &this->bPowered; }; + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; + +signals: + void poweredChanged(); + void stateChanged(); + +protected: + explicit Wireless(QObject* parent = nullptr); + Q_OBJECT_BINDABLE_PROPERTY(Wireless, bool, bPowered, &Wireless::poweredChanged); + Q_OBJECT_BINDABLE_PROPERTY( + Wireless, + WirelessState::Enum, + bState, + &Wireless::stateChanged + ); + QS_DBUS_BINDABLE_PROPERTY_GROUP(Wireless, wirelessProperties); +}; + +// -- Network -- class NetworkBackend: public QObject { Q_OBJECT; public: [[nodiscard]] virtual bool isAvailable() const = 0; - virtual Device* wireless() = 0; - virtual UntypedObjectModel* allDevices() = 0; + virtual Device* wifiDevice() = 0; + virtual UntypedObjectModel* devices() = 0; protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; @@ -61,13 +112,13 @@ class Network: public QObject { QML_NAMED_ELEMENT(Network); QML_SINGLETON; - Q_PROPERTY(Device* wireless READ wireless CONSTANT); - Q_PROPERTY(UntypedObjectModel* allDevices READ allDevices CONSTANT); + Q_PROPERTY(Device* wifiDevice READ wifiDevice CONSTANT); + Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); public: explicit Network(QObject* parent = nullptr); - [[nodiscard]] Device* wireless() { return backend ? backend->wireless() : nullptr; } - [[nodiscard]] UntypedObjectModel* allDevices() { return backend ? backend->allDevices() : nullptr; } + [[nodiscard]] Device* wifiDevice() { return backend ? backend->wifiDevice() : nullptr; } + [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; } private: void findBackend(); diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index d6034b48..dd60f6cc 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -18,7 +18,7 @@ namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.service.networkmanager", QtWarningMsg); +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } const QString NM_SERVICE = "org.freedesktop.NetworkManager"; @@ -34,9 +34,9 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { return; } - this->service = new DBusNetworkManager(NM_SERVICE, NM_PATH, bus, this); + this->dbus = new DBusNetworkManager(NM_SERVICE, NM_PATH, bus, this); - if (!this->service->isValid()) { + if (!this->dbus->isValid()) { qCDebug(logNetworkManager ) << "NetworkManager service is not currently running, attempting to start it."; @@ -56,27 +56,27 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { void NetworkManager::init() { QObject::connect( - this->service, + this->dbus, &DBusNetworkManager::DeviceAdded, this, &NetworkManager::onDeviceAdded ); QObject::connect( - this->service, + this->dbus, &DBusNetworkManager::DeviceRemoved, this, &NetworkManager::onDeviceRemoved ); - this->serviceProperties.setInterface(this->service); - this->serviceProperties.updateAllViaGetAll(); + this->dbusProperties.setInterface(this->dbus); + this->dbusProperties.updateAllViaGetAll(); this->registerDevices(); } void NetworkManager::registerDevices() { - auto pending = this->service->GetAllDevices(); + auto pending = this->dbus->GetAllDevices(); auto* call = new QDBusPendingCallWatcher(pending, this); auto responseCallback = [this](QDBusPendingCallWatcher* call) { @@ -134,8 +134,8 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { } } -UntypedObjectModel* NetworkManager::allDevices() { return &this->mDevices; } -NMDevice* NetworkManager::wireless() { return &this->mWireless; } -bool NetworkManager::isAvailable() const { return this->service && this->service->isValid(); } +UntypedObjectModel* NetworkManager::devices() { return &this->mDevices; } +NMDevice* NetworkManager::wifiDevice() { return &this->mWifi; } +bool NetworkManager::isAvailable() const { return this->dbus && this->dbus->isValid(); } } // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index b875b68d..81f86633 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -23,10 +23,13 @@ class NetworkManager: public NetworkBackend { public: explicit NetworkManager(QObject* parent = nullptr); - UntypedObjectModel* allDevices() override; - NMDevice* wireless() override; + UntypedObjectModel* devices() override; + NMDevice* wifiDevice() override; [[nodiscard]] bool isAvailable() const override; +signals: + void wifiPoweredChanged(); + private slots: void onDeviceAdded(const QDBusObjectPath& path); void onDeviceRemoved(const QDBusObjectPath& path); @@ -38,11 +41,11 @@ private slots: QHash mDeviceHash; ObjectModel mDevices {this}; - NMDevice mWireless; + NMDevice mWifi; - QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, serviceProperties); + QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); - DBusNetworkManager* service = nullptr; + DBusNetworkManager* dbus = nullptr; }; } // namespace qs::network diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index 89e31192..74cfa8e7 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -10,6 +10,7 @@ #include "../dbus/properties.hpp" #include "dbus_nm_device.h" +#include "wireless.hpp" using namespace qs::dbus; @@ -30,10 +31,27 @@ void NMDevice::init(const QString& path) { return; } + // If device type is wifi + if (this->device->property("type").toUInt() == 2) { + registerWireless(path); + } this->deviceProperties.setInterface(this->device); this->deviceProperties.updateAllViaGetAll(); } +void NMDevice::registerWireless(const QString& path) { + auto* device = new NMDevice(this); + device->init(path); + + if (!device->isValid()) { + qCWarning(logNMDevice) << "Ignoring invalid NMWireless registration of" << path; + delete device; + return; + } + + qCDebug(logNMDevice) << "Registered NMDevice" << path; +} + bool NMDevice::isValid() const { return this->device && this->device->isValid(); } QString NMDevice::address() const { return this->device ? this->device->service() : QString(); } QString NMDevice::path() const { return this->device ? this->device->path() : QString(); } diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp index 0c7e8a39..ee5085f6 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -13,6 +13,7 @@ #include "../../dbus/properties.hpp" #include "../api.hpp" #include "dbus_nm_device.h" +#include "wireless.hpp" namespace qs::network { @@ -22,15 +23,26 @@ class NMDevice: public Device { public: explicit NMDevice(QObject* parent = nullptr); void init(const QString& path); + void registerWireless(const QString& path); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; +signals: + void typeChanged(); + private: + // Needed to check if WiFi + [[nodiscard]] QBindable bindableType() const { return &this->bType; }; + Q_PROPERTY(quint32 type READ default NOTIFY typeChanged BINDABLE bindableType); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, quint32, bType, &Device::nameChanged); + QS_DBUS_PROPERTY_BINDING(NMDevice, pType, bType, deviceProperties, "DeviceType"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bName, deviceProperties, "Interface"); QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bAddress, deviceProperties, "HwAddress"); DBusNMDevice* device = nullptr; + NMWireless* wireless = nullptr; }; } // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml index 9a2b6914..f230778f 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.Device.xml @@ -2,7 +2,8 @@ + + - diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp new file mode 100644 index 00000000..21feae6f --- /dev/null +++ b/src/network/nm/wireless.cpp @@ -0,0 +1,41 @@ +#include "wireless.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "dbus_nm_wireless.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.wireless", QtWarningMsg); +} + +NMWireless::NMWireless(QObject* parent): Wireless(parent) {} + +void NMWireless::init(const QString& path) { + this->wireless = + new DBusNMWireless("org.freedesktop.NetworkManager", path, QDBusConnection::systemBus(), this); + + if (!this->wireless->isValid()) { + qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; + return; + } + + this->wirelessProperties.setInterface(this->wireless); + this->wirelessProperties.updateAllViaGetAll(); +} + +bool NMWireless::isValid() const { return this->wireless && this->wireless->isValid(); } +QString NMWireless::address() const { return this->wireless ? this->wireless->service() : QString(); } +QString NMWireless::path() const { return this->wireless ? this->wireless->path() : QString(); } + +} // namespace qs::network diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp new file mode 100644 index 00000000..b8b0364b --- /dev/null +++ b/src/network/nm/wireless.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../api.hpp" +#include "dbus_nm_wireless.h" + +namespace qs::network { + +class NMWireless: public Wireless { + Q_OBJECT; + +public: + explicit NMWireless(QObject* parent = nullptr); + void disconnect() override; + void scan() override; + void setPowered(bool powered) override; + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + +private: + DBusNMWireless* wireless = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 139ff9f9..6284c3bd 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -16,7 +16,7 @@ FloatingWindow { ListView { Layout.fillWidth: true Layout.fillHeight: true - model: Network.allDevices + model: Network.devices delegate: WrapperRectangle { width: parent.width From 87787dce24518a7e2286b3ee961596531568a3e7 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Mon, 14 Jul 2025 11:33:44 -0500 Subject: [PATCH 20/32] feat: frontend slots --- src/network/CMakeLists.txt | 8 ++--- src/network/api.cpp | 18 +++++++++++ src/network/api.hpp | 49 ++++++++++++++++------------- src/network/nm/CMakeLists.txt | 32 ------------------- src/network/nm/adapters.cpp | 47 ++++++++++++++++++++++++++++ src/network/nm/adapters.hpp | 46 +++++++++++++++++++++++++++ src/network/nm/backend.cpp | 43 +++++++++++++++---------- src/network/nm/backend.hpp | 13 +++----- src/network/nm/device.cpp | 59 ----------------------------------- src/network/nm/device.hpp | 48 ---------------------------- src/network/nm/wireless.cpp | 41 ------------------------ src/network/nm/wireless.hpp | 35 --------------------- 12 files changed, 172 insertions(+), 267 deletions(-) delete mode 100644 src/network/nm/CMakeLists.txt create mode 100644 src/network/nm/adapters.cpp create mode 100644 src/network/nm/adapters.hpp delete mode 100644 src/network/nm/device.cpp delete mode 100644 src/network/nm/device.hpp delete mode 100644 src/network/nm/wireless.cpp delete mode 100644 src/network/nm/wireless.hpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index c5b556a3..718665b0 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,6 +1,6 @@ # NetworkManager DBus set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES - CLASSNAME DBusNetworkManager + CLASSNAME DBusNetworkManagerProxy NO_NAMESPACE TRUE ) @@ -10,7 +10,7 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES ) set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNMDevice + CLASSNAME DBusNMDeviceProxy NO_NAMESPACE TRUE ) @@ -20,7 +20,7 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES ) set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES - CLASSNAME DBusNMWireless + CLASSNAME DBusNMWirelessProxy NO_NAMESPACE TRUE ) @@ -32,7 +32,7 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES qt_add_library(quickshell-network STATIC api.cpp nm/backend.cpp - nm/device.cpp + nm/adapters.cpp ${NM_DBUS_INTERFACES} ) diff --git a/src/network/api.cpp b/src/network/api.cpp index 9771f28d..43c1dbf8 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -18,6 +18,24 @@ Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); Device::Device(QObject* parent): QObject(parent) {}; +void Device::setName(const QString& name) { + if (name != this->bName) { + this->bName = name; + } +} + +void Device::setAddress(const QString& address) { + if (address != this->bAddress) { + this->bAddress = address; + } +} + +void WirelessDevice::setState(WirelessState::Enum state) { + if (state != this->bState) { + this->bState = state; + } +} + Network::Network(QObject* parent): QObject(parent) { // Try each backend diff --git a/src/network/api.hpp b/src/network/api.hpp index a5dfbd21..67c4319a 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -11,7 +11,6 @@ #include #include "../core/model.hpp" -#include "../dbus/properties.hpp" namespace qs::network { @@ -27,19 +26,22 @@ class Device: public QObject { Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); // clang-format on -public: - [[nodiscard]] QBindable bindableName() const { return &this->bName; }; - [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; - signals: void nameChanged(); void addressChanged(); -protected: +public slots: + void setName(const QString& name); + void setAddress(const QString& address); + +public: explicit Device(QObject* parent = nullptr); + [[nodiscard]] QBindable bindableName() const { return &this->bName; }; + [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; + +private: Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bName, &Device::nameChanged); Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bAddress, &Device::addressChanged); - QS_DBUS_BINDABLE_PROPERTY_GROUP(Device, deviceProperties); }; // -- Wireless Device -- @@ -60,14 +62,24 @@ class WirelessState: public QObject { Q_ENUM(Enum); }; -class Wireless: public QObject { +class WirelessDevice: public Device { Q_OBJECT; + // clang-format off Q_PROPERTY(bool powered READ powered WRITE setPowered NOTIFY poweredChanged); Q_PROPERTY(WirelessState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); //clang-format on +signals: + void poweredChanged(); + void stateChanged(); + +private slots: + void setState(WirelessState::Enum state); + public: + explicit WirelessDevice(QObject* parent = nullptr); + Q_INVOKABLE virtual void disconnect() = 0; Q_INVOKABLE virtual void scan() = 0; @@ -77,20 +89,14 @@ class Wireless: public QObject { [[nodiscard]] QBindable bindablePowered() { return &this->bPowered; }; [[nodiscard]] QBindable bindableState() const { return &this->bState; }; -signals: - void poweredChanged(); - void stateChanged(); - -protected: - explicit Wireless(QObject* parent = nullptr); - Q_OBJECT_BINDABLE_PROPERTY(Wireless, bool, bPowered, &Wireless::poweredChanged); +private: + Q_OBJECT_BINDABLE_PROPERTY(WirelessDevice, bool, bPowered, &WirelessDevice::poweredChanged); Q_OBJECT_BINDABLE_PROPERTY( - Wireless, + WirelessDevice, WirelessState::Enum, bState, - &Wireless::stateChanged + &WirelessDevice::stateChanged ); - QS_DBUS_BINDABLE_PROPERTY_GROUP(Wireless, wirelessProperties); }; // -- Network -- @@ -100,7 +106,7 @@ class NetworkBackend: public QObject { public: [[nodiscard]] virtual bool isAvailable() const = 0; - virtual Device* wifiDevice() = 0; + // virtual WirelessDevice* wifiDevice() = 0; virtual UntypedObjectModel* devices() = 0; protected: @@ -112,16 +118,15 @@ class Network: public QObject { QML_NAMED_ELEMENT(Network); QML_SINGLETON; - Q_PROPERTY(Device* wifiDevice READ wifiDevice CONSTANT); + // Q_PROPERTY(WirelessDevice* wifi READ wifiDevice CONSTANT); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); public: explicit Network(QObject* parent = nullptr); - [[nodiscard]] Device* wifiDevice() { return backend ? backend->wifiDevice() : nullptr; } + // [[nodiscard]] WirelessDevice* wifi() { return backend ? backend->wifiDevice() : nullptr; } [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; } private: - void findBackend(); class NetworkBackend* backend = nullptr; }; diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt deleted file mode 100644 index 0ad7bdcc..00000000 --- a/src/network/nm/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES - CLASSNAME DBusNetworkManager - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.xml - dbus_nm_backend -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNMDevice - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.xml - dbus_nm_device -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES - CLASSNAME DBusNMWireless - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.Wireless.xml - dbus_nm_wireless -) - -set(NM_DBUS_INTERFACES ${NM_DBUS_INTERFACES} PARENT_SCOPE) -set(NM_DBUS_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE) diff --git a/src/network/nm/adapters.cpp b/src/network/nm/adapters.cpp new file mode 100644 index 00000000..a7f9c78a --- /dev/null +++ b/src/network/nm/adapters.cpp @@ -0,0 +1,47 @@ +#include "adapters.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "dbus_nm_device.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); +} + +NMDeviceAdapter::NMDeviceAdapter(QObject* parent): QObject(parent) {} + +void NMDeviceAdapter::init(const QString& path) { + this->proxy = new DBusNMDeviceProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; + return; + } + + this->deviceProperties.setInterface(this->proxy); + this->deviceProperties.updateAllViaGetAll(); +} + +bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMDeviceAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +} // namespace qs::network diff --git a/src/network/nm/adapters.hpp b/src/network/nm/adapters.hpp new file mode 100644 index 00000000..cbbedced --- /dev/null +++ b/src/network/nm/adapters.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "../api.hpp" +#include "dbus_nm_device.h" + +namespace qs::network { + +class NMDeviceAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMDeviceAdapter(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] QString getInterface() { return this->bInterface; }; + [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; + +signals: + void interfaceChanged(const QString& interface); + void hwAddressChanged(const QString& hwAddress); + +private: + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); + + DBusNMDeviceProxy* proxy = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index dd60f6cc..af55c75c 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -14,6 +14,7 @@ #include "../../dbus/properties.hpp" #include "../api.hpp" #include "dbus_nm_backend.h" +#include "adapters.hpp" namespace qs::network { @@ -34,9 +35,9 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { return; } - this->dbus = new DBusNetworkManager(NM_SERVICE, NM_PATH, bus, this); + this->proxy = new DBusNetworkManagerProxy(NM_SERVICE, NM_PATH, bus, this); - if (!this->dbus->isValid()) { + if (!this->proxy->isValid()) { qCDebug(logNetworkManager ) << "NetworkManager service is not currently running, attempting to start it."; @@ -56,27 +57,27 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { void NetworkManager::init() { QObject::connect( - this->dbus, - &DBusNetworkManager::DeviceAdded, + this->proxy, + &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDeviceAdded ); QObject::connect( - this->dbus, - &DBusNetworkManager::DeviceRemoved, + this->proxy, + &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDeviceRemoved ); - this->dbusProperties.setInterface(this->dbus); + this->dbusProperties.setInterface(this->proxy); this->dbusProperties.updateAllViaGetAll(); this->registerDevices(); } void NetworkManager::registerDevices() { - auto pending = this->dbus->GetAllDevices(); + auto pending = this->proxy->GetAllDevices(); auto* call = new QDBusPendingCallWatcher(pending, this); auto responseCallback = [this](QDBusPendingCallWatcher* call) { @@ -98,22 +99,30 @@ void NetworkManager::registerDevices() { void NetworkManager::registerDevice(const QString& path) { if (this->mDeviceHash.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of NMDevice" << path; + qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path; return; } - auto* device = new NMDevice(this); - device->init(path); + auto* device = new Device(this); + auto* deviceAdapter = new NMDeviceAdapter(device); - if (!device->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid NMDevice registration of" << path; + deviceAdapter->init(path); + + if (!deviceAdapter->isValid()) { + qCWarning(logNetworkManager) << "Ignoring invalid Device registration of" << path; delete device; return; } + + device->setAddress(deviceAdapter->getHwAddress()); + device->setName(deviceAdapter->getInterface()); + QObject::connect(deviceAdapter, &NMDeviceAdapter::hwAddressChanged, device, &Device::setAddress); + QObject::connect(deviceAdapter, &NMDeviceAdapter::interfaceChanged, device, &Device::setName); + this->mDeviceHash.insert(path, device); this->mDevices.insertObject(device); - qCDebug(logNetworkManager) << "Registered NMDevice" << path; + qCDebug(logNetworkManager) << "Registered Device" << path; } void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { @@ -130,12 +139,12 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { auto* device = iter.value(); this->mDeviceHash.erase(iter); this->mDevices.removeObject(device); - qCDebug(logNetworkManager) << "NMDevice" << device->path() << "removed."; + qCDebug(logNetworkManager) << "Device" << path.path() << "removed."; } } UntypedObjectModel* NetworkManager::devices() { return &this->mDevices; } -NMDevice* NetworkManager::wifiDevice() { return &this->mWifi; } -bool NetworkManager::isAvailable() const { return this->dbus && this->dbus->isValid(); } +// WirelessDevice* NetworkManager::wifiDevice() { return &this->mWifi; } +bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); } } // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index 81f86633..4b597d3d 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -13,7 +13,6 @@ #include "../../dbus/properties.hpp" #include "../api.hpp" #include "dbus_nm_backend.h" -#include "device.hpp" namespace qs::network { @@ -24,12 +23,9 @@ class NetworkManager: public NetworkBackend { explicit NetworkManager(QObject* parent = nullptr); UntypedObjectModel* devices() override; - NMDevice* wifiDevice() override; + // WirelessDevice* wifiDevice() override; [[nodiscard]] bool isAvailable() const override; -signals: - void wifiPoweredChanged(); - private slots: void onDeviceAdded(const QDBusObjectPath& path); void onDeviceRemoved(const QDBusObjectPath& path); @@ -39,13 +35,12 @@ private slots: void registerDevice(const QString& path); void registerDevices(); - QHash mDeviceHash; - ObjectModel mDevices {this}; - NMDevice mWifi; + QHash mDeviceHash; + ObjectModel mDevices {this}; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); - DBusNetworkManager* dbus = nullptr; + DBusNetworkManagerProxy* proxy = nullptr; }; } // namespace qs::network diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp deleted file mode 100644 index 74cfa8e7..00000000 --- a/src/network/nm/device.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "device.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../dbus/properties.hpp" -#include "dbus_nm_device.h" -#include "wireless.hpp" - -using namespace qs::dbus; - -namespace qs::network { - -namespace { -Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); -} - -NMDevice::NMDevice(QObject* parent): Device(parent) {} - -void NMDevice::init(const QString& path) { - this->device = - new DBusNMDevice("org.freedesktop.NetworkManager", path, QDBusConnection::systemBus(), this); - - if (!this->device->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; - return; - } - - // If device type is wifi - if (this->device->property("type").toUInt() == 2) { - registerWireless(path); - } - this->deviceProperties.setInterface(this->device); - this->deviceProperties.updateAllViaGetAll(); -} - -void NMDevice::registerWireless(const QString& path) { - auto* device = new NMDevice(this); - device->init(path); - - if (!device->isValid()) { - qCWarning(logNMDevice) << "Ignoring invalid NMWireless registration of" << path; - delete device; - return; - } - - qCDebug(logNMDevice) << "Registered NMDevice" << path; -} - -bool NMDevice::isValid() const { return this->device && this->device->isValid(); } -QString NMDevice::address() const { return this->device ? this->device->service() : QString(); } -QString NMDevice::path() const { return this->device ? this->device->path() : QString(); } - -} // namespace qs::network diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp deleted file mode 100644 index ee5085f6..00000000 --- a/src/network/nm/device.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../api.hpp" -#include "dbus_nm_device.h" -#include "wireless.hpp" - -namespace qs::network { - -class NMDevice: public Device { - Q_OBJECT; - -public: - explicit NMDevice(QObject* parent = nullptr); - void init(const QString& path); - void registerWireless(const QString& path); - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - -signals: - void typeChanged(); - -private: - // Needed to check if WiFi - [[nodiscard]] QBindable bindableType() const { return &this->bType; }; - Q_PROPERTY(quint32 type READ default NOTIFY typeChanged BINDABLE bindableType); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, quint32, bType, &Device::nameChanged); - QS_DBUS_PROPERTY_BINDING(NMDevice, pType, bType, deviceProperties, "DeviceType"); - - QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bName, deviceProperties, "Interface"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bAddress, deviceProperties, "HwAddress"); - - DBusNMDevice* device = nullptr; - NMWireless* wireless = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp deleted file mode 100644 index 21feae6f..00000000 --- a/src/network/nm/wireless.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "wireless.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../dbus/properties.hpp" -#include "dbus_nm_wireless.h" - -using namespace qs::dbus; - -namespace qs::network { - -namespace { -Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.wireless", QtWarningMsg); -} - -NMWireless::NMWireless(QObject* parent): Wireless(parent) {} - -void NMWireless::init(const QString& path) { - this->wireless = - new DBusNMWireless("org.freedesktop.NetworkManager", path, QDBusConnection::systemBus(), this); - - if (!this->wireless->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; - return; - } - - this->wirelessProperties.setInterface(this->wireless); - this->wirelessProperties.updateAllViaGetAll(); -} - -bool NMWireless::isValid() const { return this->wireless && this->wireless->isValid(); } -QString NMWireless::address() const { return this->wireless ? this->wireless->service() : QString(); } -QString NMWireless::path() const { return this->wireless ? this->wireless->path() : QString(); } - -} // namespace qs::network diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp deleted file mode 100644 index b8b0364b..00000000 --- a/src/network/nm/wireless.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../api.hpp" -#include "dbus_nm_wireless.h" - -namespace qs::network { - -class NMWireless: public Wireless { - Q_OBJECT; - -public: - explicit NMWireless(QObject* parent = nullptr); - void disconnect() override; - void scan() override; - void setPowered(bool powered) override; - void init(const QString& path); - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - -private: - DBusNMWireless* wireless = nullptr; -}; - -} // namespace qs::network From bdaaa5f633bd8e3246a71aea3844f77a0cef3e62 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Mon, 14 Jul 2025 18:28:04 -0500 Subject: [PATCH 21/32] feat: state, working subdevices --- src/network/CMakeLists.txt | 4 +- src/network/api.cpp | 22 ++- src/network/api.hpp | 79 ++++---- src/network/nm/adapters.cpp | 47 ----- src/network/nm/adapters.hpp | 46 ----- ...freedesktop.NetworkManager.AccessPoint.xml | 11 -- ...desktop.NetworkManager.Device.Wireless.xml | 18 -- .../org.freedesktop.NetworkManager.Device.xml | 9 - .../nm/org.freedesktop.NetworkManager.xml | 19 -- src/network/nm_adapters.cpp | 95 ++++++++++ src/network/nm_adapters.hpp | 168 ++++++++++++++++++ .../{nm/backend.cpp => nm_backend.cpp} | 92 ++++++++-- .../{nm/backend.hpp => nm_backend.hpp} | 14 +- .../org.freedesktop.NetworkManager.Device.xml | 3 +- src/network/test/network.qml | 1 + 15 files changed, 409 insertions(+), 219 deletions(-) delete mode 100644 src/network/nm/adapters.cpp delete mode 100644 src/network/nm/adapters.hpp delete mode 100644 src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml delete mode 100644 src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml delete mode 100644 src/network/nm/org.freedesktop.NetworkManager.Device.xml delete mode 100644 src/network/nm/org.freedesktop.NetworkManager.xml create mode 100644 src/network/nm_adapters.cpp create mode 100644 src/network/nm_adapters.hpp rename src/network/{nm/backend.cpp => nm_backend.cpp} (62%) rename src/network/{nm/backend.hpp => nm_backend.hpp} (64%) diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 718665b0..42f43d5d 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -31,8 +31,8 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES qt_add_library(quickshell-network STATIC api.cpp - nm/backend.cpp - nm/adapters.cpp + nm_backend.cpp + nm_adapters.cpp ${NM_DBUS_INTERFACES} ) diff --git a/src/network/api.cpp b/src/network/api.cpp index 43c1dbf8..67efebcc 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -8,7 +8,7 @@ #include #include -#include "nm/backend.hpp" +#include "nm_backend.hpp" namespace qs::network { @@ -17,6 +17,18 @@ Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } Device::Device(QObject* parent): QObject(parent) {}; +WirelessDevice::WirelessDevice(QObject* parent): Device(parent) {}; + +QString DeviceState::toString(DeviceState::Enum state) { + switch (state) { + case DeviceState::Unknown: return QStringLiteral("Unknown"); + case DeviceState::Disconnected: return QStringLiteral("Disconnected"); + case DeviceState::Connecting: return QStringLiteral("Connecting"); + case DeviceState::Connected: return QStringLiteral("Connected"); + case DeviceState::Disconnecting: return QStringLiteral("Disconnecting"); + default: return QStringLiteral("Unknown"); + } +} void Device::setName(const QString& name) { if (name != this->bName) { @@ -30,12 +42,18 @@ void Device::setAddress(const QString& address) { } } -void WirelessDevice::setState(WirelessState::Enum state) { +void Device::setState(DeviceState::Enum state) { if (state != this->bState) { this->bState = state; } } +void WirelessDevice::setLastScan(qint64 lastScan) { + if (lastScan != this->bLastScan) { + this->bLastScan = lastScan; + } +} + Network::Network(QObject* parent): QObject(parent) { // Try each backend diff --git a/src/network/api.hpp b/src/network/api.hpp index 67c4319a..b12e25ac 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -16,6 +16,23 @@ namespace qs::network { // -- Device -- +class DeviceState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Disconnected = 10, + Connecting = 20, + Connected = 30, + Disconnecting = 40, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(DeviceState::Enum state); +}; + class Device: public QObject { Q_OBJECT; QML_ELEMENT; @@ -24,79 +41,57 @@ class Device: public QObject { // clang-format off Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); + Q_PROPERTY(DeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); // clang-format on signals: void nameChanged(); void addressChanged(); + void stateChanged(); public slots: void setName(const QString& name); void setAddress(const QString& address); + void setState(DeviceState::Enum state); public: explicit Device(QObject* parent = nullptr); [[nodiscard]] QBindable bindableName() const { return &this->bName; }; [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; private: Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bName, &Device::nameChanged); Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bAddress, &Device::addressChanged); + Q_OBJECT_BINDABLE_PROPERTY(Device, DeviceState::Enum, bState, &Device::stateChanged); }; // -- Wireless Device -- - -class WirelessState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Disconnected = 10, - Connecting = 20, - Connected = 30, - Disconnecting = 40, - }; - Q_ENUM(Enum); -}; - class WirelessDevice: public Device { Q_OBJECT; // clang-format off - Q_PROPERTY(bool powered READ powered WRITE setPowered NOTIFY poweredChanged); - Q_PROPERTY(WirelessState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); //clang-format on signals: - void poweredChanged(); - void stateChanged(); + void lastScanChanged(); -private slots: - void setState(WirelessState::Enum state); +public slots: + void setLastScan(qint64 lastScan); public: explicit WirelessDevice(QObject* parent = nullptr); - Q_INVOKABLE virtual void disconnect() = 0; - Q_INVOKABLE virtual void scan() = 0; + // Q_INVOKABLE void disconnect(); + // Q_INVOKABLE void scan(); - [[nodiscard]] bool powered() const { return this->bPowered; }; - virtual void setPowered(bool powered) = 0; - - [[nodiscard]] QBindable bindablePowered() { return &this->bPowered; }; - [[nodiscard]] QBindable bindableState() const { return &this->bState; }; + [[nodiscard]] QBindable bindableLastScan() { return &this->bLastScan; }; private: - Q_OBJECT_BINDABLE_PROPERTY(WirelessDevice, bool, bPowered, &WirelessDevice::poweredChanged); - Q_OBJECT_BINDABLE_PROPERTY( - WirelessDevice, - WirelessState::Enum, - bState, - &WirelessDevice::stateChanged - ); + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(WirelessDevice, qint64, bLastScan, &WirelessDevice::lastScanChanged); + // clang-format on }; // -- Network -- @@ -106,8 +101,8 @@ class NetworkBackend: public QObject { public: [[nodiscard]] virtual bool isAvailable() const = 0; - // virtual WirelessDevice* wifiDevice() = 0; virtual UntypedObjectModel* devices() = 0; + virtual WirelessDevice* defaultWifiDevice() = 0; protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; @@ -118,13 +113,15 @@ class Network: public QObject { QML_NAMED_ELEMENT(Network); QML_SINGLETON; - // Q_PROPERTY(WirelessDevice* wifi READ wifiDevice CONSTANT); + Q_PROPERTY(WirelessDevice* defaultWifiDevice READ defaultWifiDevice CONSTANT); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); public: explicit Network(QObject* parent = nullptr); - // [[nodiscard]] WirelessDevice* wifi() { return backend ? backend->wifiDevice() : nullptr; } - [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; } + [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; }; + [[nodiscard]] WirelessDevice* defaultWifiDevice() { + return backend ? backend->defaultWifiDevice() : nullptr; + }; private: class NetworkBackend* backend = nullptr; diff --git a/src/network/nm/adapters.cpp b/src/network/nm/adapters.cpp deleted file mode 100644 index a7f9c78a..00000000 --- a/src/network/nm/adapters.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "adapters.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../dbus/properties.hpp" -#include "dbus_nm_device.h" - -using namespace qs::dbus; - -namespace qs::network { - -namespace { -Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); -} - -NMDeviceAdapter::NMDeviceAdapter(QObject* parent): QObject(parent) {} - -void NMDeviceAdapter::init(const QString& path) { - this->proxy = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMDevice for" << path; - return; - } - - this->deviceProperties.setInterface(this->proxy); - this->deviceProperties.updateAllViaGetAll(); -} - -bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMDeviceAdapter::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network diff --git a/src/network/nm/adapters.hpp b/src/network/nm/adapters.hpp deleted file mode 100644 index cbbedced..00000000 --- a/src/network/nm/adapters.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../api.hpp" -#include "dbus_nm_device.h" - -namespace qs::network { - -class NMDeviceAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMDeviceAdapter(QObject* parent = nullptr); - void init(const QString& path); - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QString getInterface() { return this->bInterface; }; - [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; - -signals: - void interfaceChanged(const QString& interface); - void hwAddressChanged(const QString& hwAddress); - -private: - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); - - DBusNMDeviceProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml deleted file mode 100644 index 33872050..00000000 --- a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml deleted file mode 100644 index 17b712bb..00000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - arg name="access_ponit" type="o"/> - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml deleted file mode 100644 index f230778f..00000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.xml b/src/network/nm/org.freedesktop.NetworkManager.xml deleted file mode 100644 index 4f9ca03e..00000000 --- a/src/network/nm/org.freedesktop.NetworkManager.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp new file mode 100644 index 00000000..96d82225 --- /dev/null +++ b/src/network/nm_adapters.cpp @@ -0,0 +1,95 @@ +#include "nm_adapters.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "dbus_nm_device.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); +} + +NMDeviceAdapter::NMDeviceAdapter(QObject* parent): QObject(parent) {} + +void NMDeviceAdapter::init(const QString& path) { + this->proxy = new DBusNMDeviceProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNMDevice) << "Cannot create NMDeviceAdapter for" << path; + return; + } + + this->deviceProperties.setInterface(this->proxy); + this->deviceProperties.updateAllViaGetAll(); +} + +bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMDeviceAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +DeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { + switch(state) { + case 0 ... 20: return DeviceState::Unknown; + case 30: return DeviceState::Disconnected; + case 40 ... 90: return DeviceState::Connecting; + case 100: return DeviceState::Connected; + case 110 ... 120: return DeviceState::Disconnecting; + } +} + +NMWirelessAdapter::NMWirelessAdapter(QObject* parent): QObject(parent) {} + +void NMWirelessAdapter::init(const QString& path) { + this->proxy = new DBusNMWirelessProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNMDevice) << "Cannot create NMWirelessAdapter for" << path; + return; + } + + this->wirelessProperties.setInterface(this->proxy); + this->wirelessProperties.updateAllViaGetAll(); +} + +bool NMWirelessAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMWirelessAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } +} // namespace qs::network + +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp new file mode 100644 index 00000000..f12e8609 --- /dev/null +++ b/src/network/nm_adapters.hpp @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../dbus/properties.hpp" +#include "api.hpp" +#include "dbus_nm_device.h" +#include "dbus_nm_wireless.h" + +namespace qs::network { + +class NMDeviceState: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unkown = 0, + Unmanaged = 10, + Unavailable = 20, + Disconnected = 30, + Prepare = 40, + Config = 50, + NeedAuth = 60, + IPConfig = 70, + IPCheck = 80, + Secondaries = 90, + Activated = 100, + Deactivating = 110, + Failed = 120, + }; + Q_ENUM(Enum); + + static DeviceState::Enum translate(NMDeviceState::Enum state); +}; + +class NMDeviceType: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unknown = 0, + Generic = 14, + Ethernet = 1, + Wifi = 2, + Unused1 = 3, + Unused2 = 4, + Bluetooth = 5, + OlpcMesh = 6, + Wimax = 7, + Modem = 8, + Infiniband = 9, + Bond = 10, + Vlan = 11, + Adsl = 12, + Bridge = 13, + Team = 15, + Tun = 16, + Tunnel = 17, + Macvlan = 18, + Vxlan = 19, + Veth = 20, + Macsec = 21, + Dummy = 22, + Ppp = 23, + OvsInterface = 24, + OvsPort = 25, + OvsBridge = 26, + Wpan = 27, + Lowpan = 28, + WireGuard = 29, + WifiP2P = 30, + Vrf = 31, + Loopback = 32, + Hsr = 33, + Ipvlan = 34, + }; + Q_ENUM(Enum); +}; + +} // namespace qs::network + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMDeviceType::Enum; + static DBusResult fromWire(Wire wire); +}; + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMDeviceState::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::network { + +class NMDeviceAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMDeviceAdapter(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] QString getInterface() { return this->bInterface; }; + [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; + [[nodiscard]] NMDeviceType::Enum getType() { return this->bType; }; + +signals: + void interfaceChanged(const QString& interface); + void hwAddressChanged(const QString& hwAddress); + void typeChanged(NMDeviceType::Enum type); + void stateChanged(NMDeviceState::Enum state); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceState::Enum, bState, &NMDeviceAdapter::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceType::Enum, bType, &NMDeviceAdapter::typeChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pType, bType, deviceProperties, "DeviceType"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pState, bState, deviceProperties, "State"); + + DBusNMDeviceProxy* proxy = nullptr; +}; + +class NMWirelessAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMWirelessAdapter(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; + +signals: + void lastScanChanged(qint64 time); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); + QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); + + DBusNMWirelessProxy* proxy = nullptr; +}; +} // namespace qs::network diff --git a/src/network/nm/backend.cpp b/src/network/nm_backend.cpp similarity index 62% rename from src/network/nm/backend.cpp rename to src/network/nm_backend.cpp index af55c75c..c04cc2a2 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm_backend.cpp @@ -1,4 +1,4 @@ -#include "backend.hpp" +#include "nm_backend.hpp" #include #include @@ -10,11 +10,11 @@ #include #include -#include "../../dbus/bus.hpp" -#include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../dbus/bus.hpp" +#include "../dbus/properties.hpp" +#include "api.hpp" #include "dbus_nm_backend.h" -#include "adapters.hpp" +#include "nm_adapters.hpp" namespace qs::network { @@ -87,7 +87,7 @@ void NetworkManager::registerDevices() { qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); } else { for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerDevice(devicePath.path()); + this->queueDeviceRegistration(devicePath.path()); } } @@ -97,36 +97,87 @@ void NetworkManager::registerDevices() { QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } -void NetworkManager::registerDevice(const QString& path) { +void NetworkManager::queueDeviceRegistration(const QString& path) { if (this->mDeviceHash.contains(path)) { qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path; return; } - auto* device = new Device(this); - auto* deviceAdapter = new NMDeviceAdapter(device); - + // Create a device adapter + auto* deviceAdapter = new NMDeviceAdapter(); deviceAdapter->init(path); if (!deviceAdapter->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid Device registration of" << path; - delete device; + qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; return; } - - device->setAddress(deviceAdapter->getHwAddress()); - device->setName(deviceAdapter->getInterface()); + + // Wait for DBus to tell us the device type + QObject::connect( + deviceAdapter, + &NMDeviceAdapter::typeChanged, + this, + [this, deviceAdapter, path](NMDeviceType::Enum type) { + this->registerDevice(deviceAdapter, type, path); + } + ); +} + +// Register the device +void NetworkManager::registerDevice( + NMDeviceAdapter* deviceAdapter, + NMDeviceType::Enum type, + const QString& path +) { + Device* device = createDeviceVariant(type, path); + + // Bind backend NMDeviceAdapter to frontend Device + deviceAdapter->setParent(device); QObject::connect(deviceAdapter, &NMDeviceAdapter::hwAddressChanged, device, &Device::setAddress); QObject::connect(deviceAdapter, &NMDeviceAdapter::interfaceChanged, device, &Device::setName); - + QObject::connect( + deviceAdapter, + &NMDeviceAdapter::stateChanged, + device, + [device](NMDeviceState::Enum state) { device->setState(NMDeviceState::translate(state)); } + ); this->mDeviceHash.insert(path, device); this->mDevices.insertObject(device); - qCDebug(logNetworkManager) << "Registered Device" << path; + + qCDebug(logNetworkManager) << "Registered device at path" << path; +} + +// Create a derived device class based on the NMDeviceType of the NMDeviceAdapter +Device* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, const QString& path) { + switch (type) { + case NMDeviceType::Wifi: return this->bindWirelessDevice(path); + default: return new Device(); + } +} + +// Create a frontend WirelessDevice and bind the backend wireless adapter to it +WirelessDevice* NetworkManager::bindWirelessDevice(const QString& path) { + auto* device = new WirelessDevice(this); + + auto* wirelessAdapter = new NMWirelessAdapter(device); + wirelessAdapter->init(path); + + QObject::connect( + wirelessAdapter, + &NMWirelessAdapter::lastScanChanged, + device, + &WirelessDevice::setLastScan + ); + + if (this->mWifi == nullptr) { + this->mWifi = device; + }; + return device; } void NetworkManager::onDeviceAdded(const QDBusObjectPath& path) { - this->registerDevice(path.path()); + this->queueDeviceRegistration(path.path()); } void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { @@ -137,6 +188,9 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { << "which is not registered."; } else { auto* device = iter.value(); + if (this->mWifi == device) { + this->mWifi = nullptr; + }; this->mDeviceHash.erase(iter); this->mDevices.removeObject(device); qCDebug(logNetworkManager) << "Device" << path.path() << "removed."; @@ -144,7 +198,7 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { } UntypedObjectModel* NetworkManager::devices() { return &this->mDevices; } -// WirelessDevice* NetworkManager::wifiDevice() { return &this->mWifi; } +WirelessDevice* NetworkManager::defaultWifiDevice() { return this->mWifi; } bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); } } // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm_backend.hpp similarity index 64% rename from src/network/nm/backend.hpp rename to src/network/nm_backend.hpp index 4b597d3d..5aeef00d 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm_backend.hpp @@ -10,9 +10,10 @@ #include #include -#include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../dbus/properties.hpp" +#include "api.hpp" #include "dbus_nm_backend.h" +#include "nm_adapters.hpp" namespace qs::network { @@ -23,7 +24,7 @@ class NetworkManager: public NetworkBackend { explicit NetworkManager(QObject* parent = nullptr); UntypedObjectModel* devices() override; - // WirelessDevice* wifiDevice() override; + WirelessDevice* defaultWifiDevice() override; [[nodiscard]] bool isAvailable() const override; private slots: @@ -32,11 +33,16 @@ private slots: private: void init(); - void registerDevice(const QString& path); + void registerDevice(NMDeviceAdapter* deviceAdapter, NMDeviceType::Enum type, const QString& path); void registerDevices(); + void queueDeviceRegistration(const QString& path); + Device* createDeviceVariant(NMDeviceType::Enum type, const QString& path); + WirelessDevice* bindWirelessDevice(const QString& path); + Device* bindDevice(NMDeviceAdapter* deviceAdapter); QHash mDeviceHash; ObjectModel mDevices {this}; + WirelessDevice* mWifi = nullptr; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); diff --git a/src/network/org.freedesktop.NetworkManager.Device.xml b/src/network/org.freedesktop.NetworkManager.Device.xml index 9a2b6914..f230778f 100644 --- a/src/network/org.freedesktop.NetworkManager.Device.xml +++ b/src/network/org.freedesktop.NetworkManager.Device.xml @@ -2,7 +2,8 @@ + + - diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 6284c3bd..60b797ca 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -31,6 +31,7 @@ FloatingWindow { font.bold: true } Label { text: "Hardware Address: " + modelData.address } + Label { text: "State: " + DeviceState.toString(modelData.state) } } } } From c9ece29b92ce36a4173312cb2ffcc1ddc03b482d Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Tue, 15 Jul 2025 11:49:37 -0500 Subject: [PATCH 22/32] disconnect --- src/network/api.cpp | 17 +++++++++++++++++ src/network/api.hpp | 25 +++++++++++++++++++++---- src/network/nm_adapters.cpp | 1 + src/network/nm_adapters.hpp | 3 +++ src/network/nm_backend.cpp | 6 +++++- src/network/test/network.qml | 4 ++++ 6 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 67efebcc..9114ea7c 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -13,6 +13,7 @@ namespace qs::network { namespace { +Q_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } @@ -48,6 +49,22 @@ void Device::setState(DeviceState::Enum state) { } } +void Device::disconnect() { + if (this->bState == DeviceState::Disconnected) { + qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; + return; + } + + if (this->bState == DeviceState::Disconnecting) { + qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting"; + return; + } + + qCDebug(logNetworkDevice) << "Disconnecting from device" << this; + + signalDisconnect(); +} + void WirelessDevice::setLastScan(qint64 lastScan) { if (lastScan != this->bLastScan) { this->bLastScan = lastScan; diff --git a/src/network/api.hpp b/src/network/api.hpp index b12e25ac..cdcfff02 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -23,39 +23,56 @@ class DeviceState: public QObject { public: enum Enum : quint8 { + /// The device state is unknown. Unknown = 0, - Disconnected = 10, - Connecting = 20, - Connected = 30, - Disconnecting = 40, + /// The device is not connected. + Disconnected = 1, + /// The device is connected. + Connected = 2, + /// The device is disconnecting. + Disconnecting = 3, + /// The device is connecting. + Connecting = 4, }; Q_ENUM(Enum); Q_INVOKABLE static QString toString(DeviceState::Enum state); }; +///! A tracked network device. class Device: public QObject { Q_OBJECT; QML_ELEMENT; QML_UNCREATABLE("Devices can only be acquired through Network"); // clang-format off + /// The name of the device's interface. Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); + /// The hardware address of the device's interface in the XX:XX:XX:XX:XX:XX format. Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); + /// Connection state of the device. Q_PROPERTY(DeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); // clang-format on signals: + void signalDisconnect(); + + // Frontend-facing signals void nameChanged(); void addressChanged(); void stateChanged(); public slots: + // Slots for the backend to connect signals to void setName(const QString& name); void setAddress(const QString& address); void setState(DeviceState::Enum state); public: explicit Device(QObject* parent = nullptr); + + /// Disconnects the device and prevents it from automatically activating further connections. + Q_INVOKABLE void disconnect(); + [[nodiscard]] QBindable bindableName() const { return &this->bName; }; [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; [[nodiscard]] QBindable bindableState() const { return &this->bState; }; diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 96d82225..42bd3863 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -38,6 +38,7 @@ void NMDeviceAdapter::init(const QString& path) { this->deviceProperties.updateAllViaGetAll(); } +void NMDeviceAdapter::disconnect() { this->proxy->Disconnect(); } bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMDeviceAdapter::address() const { return this->proxy ? this->proxy->service() : QString(); diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp index f12e8609..ce942986 100644 --- a/src/network/nm_adapters.hpp +++ b/src/network/nm_adapters.hpp @@ -120,6 +120,9 @@ class NMDeviceAdapter: public QObject { [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; [[nodiscard]] NMDeviceType::Enum getType() { return this->bType; }; +public slots: + void disconnect(); + signals: void interfaceChanged(const QString& interface); void hwAddressChanged(const QString& hwAddress); diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index c04cc2a2..49b5d644 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -131,7 +131,7 @@ void NetworkManager::registerDevice( ) { Device* device = createDeviceVariant(type, path); - // Bind backend NMDeviceAdapter to frontend Device + // deviceAdapter signal -> Device slot deviceAdapter->setParent(device); QObject::connect(deviceAdapter, &NMDeviceAdapter::hwAddressChanged, device, &Device::setAddress); QObject::connect(deviceAdapter, &NMDeviceAdapter::interfaceChanged, device, &Device::setName); @@ -142,6 +142,10 @@ void NetworkManager::registerDevice( [device](NMDeviceState::Enum state) { device->setState(NMDeviceState::translate(state)); } ); + // Device signal -> deviceAdapter slot + QObject::connect(device, &Device::signalDisconnect, deviceAdapter, &NMDeviceAdapter::disconnect); + + // Track device this->mDeviceHash.insert(path, device); this->mDevices.insertObject(device); diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 60b797ca..4a95e760 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -32,6 +32,10 @@ FloatingWindow { } Label { text: "Hardware Address: " + modelData.address } Label { text: "State: " + DeviceState.toString(modelData.state) } + Button { + text: "Disconnect" + onClicked: modelData.disconnect() + } } } } From 997f45b4a2de7c20439d95ca237407b293215803 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Tue, 15 Jul 2025 13:54:30 -0500 Subject: [PATCH 23/32] feat: scan, lastScan, NetworkDeviceType --- src/network/api.cpp | 70 +++++++++++++++++++---------- src/network/api.hpp | 86 +++++++++++++++++++++++++----------- src/network/nm_adapters.cpp | 13 +++--- src/network/nm_adapters.hpp | 8 ++-- src/network/nm_backend.cpp | 55 +++++++++++++++++------ src/network/nm_backend.hpp | 14 +++--- src/network/test/network.qml | 22 +++++++-- 7 files changed, 184 insertions(+), 84 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 9114ea7c..4de72484 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -13,62 +13,86 @@ namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); +Q_LOGGING_CATEGORY(logNetworkNetworkDevice, "quickshell.network.device", QtWarningMsg); Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); -} +} // namespace -Device::Device(QObject* parent): QObject(parent) {}; -WirelessDevice::WirelessDevice(QObject* parent): Device(parent) {}; +NetworkDevice::NetworkDevice(QObject* parent): QObject(parent) {}; +WirelessNetworkDevice::WirelessNetworkDevice(QObject* parent): NetworkDevice(parent) {}; -QString DeviceState::toString(DeviceState::Enum state) { +QString NetworkDeviceState::toString(NetworkDeviceState::Enum state) { switch (state) { - case DeviceState::Unknown: return QStringLiteral("Unknown"); - case DeviceState::Disconnected: return QStringLiteral("Disconnected"); - case DeviceState::Connecting: return QStringLiteral("Connecting"); - case DeviceState::Connected: return QStringLiteral("Connected"); - case DeviceState::Disconnecting: return QStringLiteral("Disconnecting"); + case NetworkDeviceState::Unknown: return QStringLiteral("Unknown"); + case NetworkDeviceState::Disconnected: return QStringLiteral("Disconnected"); + case NetworkDeviceState::Connecting: return QStringLiteral("Connecting"); + case NetworkDeviceState::Connected: return QStringLiteral("Connected"); + case NetworkDeviceState::Disconnecting: return QStringLiteral("Disconnecting"); + default: return QStringLiteral("Unknown"); + } +} + +QString NetworkDeviceType::toString(NetworkDeviceType::Enum type) { + switch (type) { + case NetworkDeviceType::Other: return QStringLiteral("Other"); + case NetworkDeviceType::Wireless: return QStringLiteral("Wireless"); + case NetworkDeviceType::Ethernet: return QStringLiteral("Ethernet"); default: return QStringLiteral("Unknown"); } } -void Device::setName(const QString& name) { +void NetworkDevice::setName(const QString& name) { if (name != this->bName) { this->bName = name; } } -void Device::setAddress(const QString& address) { +void NetworkDevice::setAddress(const QString& address) { if (address != this->bAddress) { this->bAddress = address; } } -void Device::setState(DeviceState::Enum state) { +void NetworkDevice::setState(NetworkDeviceState::Enum state) { if (state != this->bState) { this->bState = state; } } -void Device::disconnect() { - if (this->bState == DeviceState::Disconnected) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; +void NetworkDevice::disconnect() { + if (this->bState == NetworkDeviceState::Disconnected) { + qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already disconnected"; return; } - if (this->bState == DeviceState::Disconnecting) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting"; + if (this->bState == NetworkDeviceState::Disconnecting) { + qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already disconnecting"; return; } - qCDebug(logNetworkDevice) << "Disconnecting from device" << this; - + qCDebug(logNetworkNetworkDevice) << "Disconnecting from device" << this; + signalDisconnect(); } -void WirelessDevice::setLastScan(qint64 lastScan) { - if (lastScan != this->bLastScan) { - this->bLastScan = lastScan; +void WirelessNetworkDevice::scanComplete(qint64 lastScan) { + this->bLastScan = lastScan; + emit this->lastScanChanged(); + + if (this->bScanning) { + this->bScanning = false; + emit this->scanningChanged(); + } +} + +void WirelessNetworkDevice::scan() { + if (this->bScanning) { + qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already scanning"; + return; } + + qCDebug(logNetworkNetworkDevice) << "Requesting scan on wireless device" << this; + this->bScanning = true; + signalScan(); } Network::Network(QObject* parent): QObject(parent) { diff --git a/src/network/api.hpp b/src/network/api.hpp index cdcfff02..ef5d8f4c 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -14,9 +14,28 @@ namespace qs::network { -// -- Device -- -class DeviceState: public QObject { +///! Type of network device. +class NetworkDeviceType: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + ///! A generic device. + Other = 0, + ///! An 802.11 Wi-Fi device. + Wireless = 1, + ///! A wired ethernet device. + Ethernet = 2 + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NetworkDeviceType::Enum type); +}; + +///! State of a network device. +class NetworkDeviceState: public QObject { Q_OBJECT; QML_ELEMENT; QML_SINGLETON; @@ -35,14 +54,14 @@ class DeviceState: public QObject { Connecting = 4, }; Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceState::Enum state); + Q_INVOKABLE static QString toString(NetworkDeviceState::Enum state); }; ///! A tracked network device. -class Device: public QObject { +class NetworkDevice: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("Devices can only be acquired through Network"); + QML_UNCREATABLE("NetworkDevices can only be acquired through Network"); // clang-format off /// The name of the device's interface. @@ -50,76 +69,89 @@ class Device: public QObject { /// The hardware address of the device's interface in the XX:XX:XX:XX:XX:XX format. Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); /// Connection state of the device. - Q_PROPERTY(DeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + Q_PROPERTY(NetworkDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + /// Type of device. + Q_PROPERTY(NetworkDeviceType::Enum type READ type CONSTANT); // clang-format on signals: - void signalDisconnect(); - - // Frontend-facing signals void nameChanged(); void addressChanged(); void stateChanged(); + // For backend slots + void signalDisconnect(); + public slots: - // Slots for the backend to connect signals to + // For backend signals void setName(const QString& name); void setAddress(const QString& address); - void setState(DeviceState::Enum state); + void setState(NetworkDeviceState::Enum state); public: - explicit Device(QObject* parent = nullptr); + explicit NetworkDevice(QObject* parent = nullptr); /// Disconnects the device and prevents it from automatically activating further connections. Q_INVOKABLE void disconnect(); + [[nodiscard]] virtual NetworkDeviceType::Enum type() const { return NetworkDeviceType::Other; }; [[nodiscard]] QBindable bindableName() const { return &this->bName; }; [[nodiscard]] QBindable bindableAddress() const { return &this->bAddress; }; - [[nodiscard]] QBindable bindableState() const { return &this->bState; }; + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; private: - Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bName, &Device::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(Device, QString, bAddress, &Device::addressChanged); - Q_OBJECT_BINDABLE_PROPERTY(Device, DeviceState::Enum, bState, &Device::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NetworkDeviceState::Enum, bState, &NetworkDevice::stateChanged); }; -// -- Wireless Device -- -class WirelessDevice: public Device { +///! Wireless variant of a tracked network device. +class WirelessNetworkDevice: public NetworkDevice { Q_OBJECT; // clang-format off + /// The timestamp (in CLOCK_BOOTTIME milliseconds) for the last finished network scan. Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); + /// True if the wireless device is currently scanning for available wifi networks. + Q_PROPERTY(bool scanning READ default NOTIFY scanningChanged BINDABLE bindableScanning); //clang-format on signals: + void signalScan(); + + // Frontend-facing signals void lastScanChanged(); + void scanningChanged(); public slots: - void setLastScan(qint64 lastScan); + // For backend signals + void scanComplete(qint64 lastScan); public: - explicit WirelessDevice(QObject* parent = nullptr); + explicit WirelessNetworkDevice(QObject* parent = nullptr); + [[nodiscard]] NetworkDeviceType::Enum type() const override { return NetworkDeviceType::Wireless; }; - // Q_INVOKABLE void disconnect(); - // Q_INVOKABLE void scan(); + /// Request the wireless device to scan for available WiFi networks. + Q_INVOKABLE void scan(); + [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; }; [[nodiscard]] QBindable bindableLastScan() { return &this->bLastScan; }; private: // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(WirelessDevice, qint64, bLastScan, &WirelessDevice::lastScanChanged); + Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, bool, bScanning, &WirelessNetworkDevice::scanningChanged); + Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, qint64, bLastScan, &WirelessNetworkDevice::lastScanChanged); // clang-format on }; // -- Network -- - class NetworkBackend: public QObject { Q_OBJECT; public: [[nodiscard]] virtual bool isAvailable() const = 0; virtual UntypedObjectModel* devices() = 0; - virtual WirelessDevice* defaultWifiDevice() = 0; + virtual WirelessNetworkDevice* defaultWifiDevice() = 0; protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; @@ -130,13 +162,13 @@ class Network: public QObject { QML_NAMED_ELEMENT(Network); QML_SINGLETON; - Q_PROPERTY(WirelessDevice* defaultWifiDevice READ defaultWifiDevice CONSTANT); + Q_PROPERTY(WirelessNetworkDevice* defaultWifiNetworkDevice READ defaultWifiNetworkDevice CONSTANT); Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); public: explicit Network(QObject* parent = nullptr); [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; }; - [[nodiscard]] WirelessDevice* defaultWifiDevice() { + [[nodiscard]] WirelessNetworkDevice* defaultWifiNetworkDevice() { return backend ? backend->defaultWifiDevice() : nullptr; }; diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 42bd3863..5ab7cfe4 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -45,13 +45,13 @@ QString NMDeviceAdapter::address() const { } QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -DeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { +NetworkDeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { switch(state) { - case 0 ... 20: return DeviceState::Unknown; - case 30: return DeviceState::Disconnected; - case 40 ... 90: return DeviceState::Connecting; - case 100: return DeviceState::Connected; - case 110 ... 120: return DeviceState::Disconnecting; + case 0 ... 20: return NetworkDeviceState::Unknown; + case 30: return NetworkDeviceState::Disconnected; + case 40 ... 90: return NetworkDeviceState::Connecting; + case 100: return NetworkDeviceState::Connected; + case 110 ... 120: return NetworkDeviceState::Disconnecting; } } @@ -74,6 +74,7 @@ void NMWirelessAdapter::init(const QString& path) { this->wirelessProperties.updateAllViaGetAll(); } +void NMWirelessAdapter::scan() { this->proxy->RequestScan(); } bool NMWirelessAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMWirelessAdapter::address() const { return this->proxy ? this->proxy->service() : QString(); diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp index ce942986..170ffc22 100644 --- a/src/network/nm_adapters.hpp +++ b/src/network/nm_adapters.hpp @@ -38,7 +38,7 @@ class NMDeviceState: public QObject { }; Q_ENUM(Enum); - static DeviceState::Enum translate(NMDeviceState::Enum state); + static NetworkDeviceState::Enum translate(NMDeviceState::Enum state); }; class NMDeviceType: public QObject { @@ -156,8 +156,11 @@ class NMWirelessAdapter: public QObject { [[nodiscard]] QString address() const; [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; +public slots: + void scan(); + signals: - void lastScanChanged(qint64 time); + void lastScanChanged(qint64 lastScan); private: // clang-format off @@ -165,7 +168,6 @@ class NMWirelessAdapter: public QObject { QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); - DBusNMWirelessProxy* proxy = nullptr; }; } // namespace qs::network diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index 49b5d644..2c53c7c5 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -129,12 +129,22 @@ void NetworkManager::registerDevice( NMDeviceType::Enum type, const QString& path ) { - Device* device = createDeviceVariant(type, path); + NetworkDevice* device = createDeviceVariant(type, path); - // deviceAdapter signal -> Device slot + // NMDeviceAdapter signal -> NetworkDevice slot deviceAdapter->setParent(device); - QObject::connect(deviceAdapter, &NMDeviceAdapter::hwAddressChanged, device, &Device::setAddress); - QObject::connect(deviceAdapter, &NMDeviceAdapter::interfaceChanged, device, &Device::setName); + QObject::connect( + deviceAdapter, + &NMDeviceAdapter::hwAddressChanged, + device, + &NetworkDevice::setAddress + ); + QObject::connect( + deviceAdapter, + &NMDeviceAdapter::interfaceChanged, + device, + &NetworkDevice::setName + ); QObject::connect( deviceAdapter, &NMDeviceAdapter::stateChanged, @@ -142,8 +152,13 @@ void NetworkManager::registerDevice( [device](NMDeviceState::Enum state) { device->setState(NMDeviceState::translate(state)); } ); - // Device signal -> deviceAdapter slot - QObject::connect(device, &Device::signalDisconnect, deviceAdapter, &NMDeviceAdapter::disconnect); + // NetworkDevice signal -> NMDeviceAdapter slot + QObject::connect( + device, + &NetworkDevice::signalDisconnect, + deviceAdapter, + &NMDeviceAdapter::disconnect + ); // Track device this->mDeviceHash.insert(path, device); @@ -153,25 +168,37 @@ void NetworkManager::registerDevice( } // Create a derived device class based on the NMDeviceType of the NMDeviceAdapter -Device* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, const QString& path) { +NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, const QString& path) { switch (type) { case NMDeviceType::Wifi: return this->bindWirelessDevice(path); - default: return new Device(); + default: return new NetworkDevice(); } } -// Create a frontend WirelessDevice and bind the backend wireless adapter to it -WirelessDevice* NetworkManager::bindWirelessDevice(const QString& path) { - auto* device = new WirelessDevice(this); +// Create a frontend WirelessNetworkDevice and bind the backend wireless adapter to it +WirelessNetworkDevice* NetworkManager::bindWirelessDevice(const QString& path) { + auto* device = new WirelessNetworkDevice(this); auto* wirelessAdapter = new NMWirelessAdapter(device); wirelessAdapter->init(path); + // NMWirelessAdapter signal -> WirelessNetworkDevice slot QObject::connect( wirelessAdapter, &NMWirelessAdapter::lastScanChanged, device, - &WirelessDevice::setLastScan + [device](qint64 lastScan) { + device->scanComplete(lastScan); + qCDebug(logNetworkManager) << lastScan; + } + ); + + // WirelessNetworkDevice signal -> NMWirelessAdapter slot + QObject::connect( + device, + &WirelessNetworkDevice::signalScan, + wirelessAdapter, + &NMWirelessAdapter::scan ); if (this->mWifi == nullptr) { @@ -197,12 +224,12 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { }; this->mDeviceHash.erase(iter); this->mDevices.removeObject(device); - qCDebug(logNetworkManager) << "Device" << path.path() << "removed."; + qCDebug(logNetworkManager) << "NetworkDevice" << path.path() << "removed."; } } UntypedObjectModel* NetworkManager::devices() { return &this->mDevices; } -WirelessDevice* NetworkManager::defaultWifiDevice() { return this->mWifi; } +WirelessNetworkDevice* NetworkManager::defaultWifiDevice() { return this->mWifi; } bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); } } // namespace qs::network diff --git a/src/network/nm_backend.hpp b/src/network/nm_backend.hpp index 5aeef00d..3e96b3e3 100644 --- a/src/network/nm_backend.hpp +++ b/src/network/nm_backend.hpp @@ -24,7 +24,7 @@ class NetworkManager: public NetworkBackend { explicit NetworkManager(QObject* parent = nullptr); UntypedObjectModel* devices() override; - WirelessDevice* defaultWifiDevice() override; + WirelessNetworkDevice* defaultWifiDevice() override; [[nodiscard]] bool isAvailable() const override; private slots: @@ -36,13 +36,13 @@ private slots: void registerDevice(NMDeviceAdapter* deviceAdapter, NMDeviceType::Enum type, const QString& path); void registerDevices(); void queueDeviceRegistration(const QString& path); - Device* createDeviceVariant(NMDeviceType::Enum type, const QString& path); - WirelessDevice* bindWirelessDevice(const QString& path); - Device* bindDevice(NMDeviceAdapter* deviceAdapter); + NetworkDevice* createDeviceVariant(NMDeviceType::Enum type, const QString& path); + WirelessNetworkDevice* bindWirelessDevice(const QString& path); + NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); - QHash mDeviceHash; - ObjectModel mDevices {this}; - WirelessDevice* mWifi = nullptr; + QHash mDeviceHash; + ObjectModel mDevices {this}; + WirelessNetworkDevice* mWifi = nullptr; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 4a95e760..357daa54 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -31,10 +31,24 @@ FloatingWindow { font.bold: true } Label { text: "Hardware Address: " + modelData.address } - Label { text: "State: " + DeviceState.toString(modelData.state) } - Button { - text: "Disconnect" - onClicked: modelData.disconnect() + Label { text: "Device type: " + NetworkDeviceType.toString(modelData.type) } + + RowLayout { + Label { text: "State: " + NetworkDeviceState.toString(modelData.state) } + Button { + text: "Disconnect" + onClicked: modelData.disconnect() + visible: modelData.state === NetworkDeviceState.Connected + } + } + RowLayout { + Label { text: "Last scan: " + modelData.lastScan } + Button { + text: "Scan" + onClicked: modelData.scan() + visible: modelData.scanning === false; + } + visible: modelData.type === NetworkDeviceType.Wireless } } } From 560232c16ae51375b4ffa45306665d5b5dd061c4 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 16 Jul 2025 14:24:38 -0500 Subject: [PATCH 24/32] feat: accessPoints --- src/network/CMakeLists.txt | 10 ++ src/network/api.cpp | 55 ++++++- src/network/api.hpp | 72 +++++++-- src/network/nm_adapters.cpp | 144 ++++++++++++++++-- src/network/nm_adapters.hpp | 59 ++++++- src/network/nm_backend.cpp | 44 +++--- src/network/nm_backend.hpp | 10 +- ...freedesktop.NetworkManager.AccessPoint.xml | 5 - ...desktop.NetworkManager.Device.Wireless.xml | 7 +- src/network/test/network.qml | 31 +++- 10 files changed, 365 insertions(+), 72 deletions(-) diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 42f43d5d..1de97dd2 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES dbus_nm_wireless ) +set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES + CLASSNAME DBusNMAccessPointProxy + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + org.freedesktop.NetworkManager.AccessPoint.xml + dbus_nm_accesspoint +) + qt_add_library(quickshell-network STATIC api.cpp nm_backend.cpp diff --git a/src/network/api.cpp b/src/network/api.cpp index 4de72484..2eb7a5ce 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -13,12 +13,13 @@ namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNetworkNetworkDevice, "quickshell.network.device", QtWarningMsg); +Q_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } // namespace +// NetworkDevice + NetworkDevice::NetworkDevice(QObject* parent): QObject(parent) {}; -WirelessNetworkDevice::WirelessNetworkDevice(QObject* parent): NetworkDevice(parent) {}; QString NetworkDeviceState::toString(NetworkDeviceState::Enum state) { switch (state) { @@ -60,20 +61,24 @@ void NetworkDevice::setState(NetworkDeviceState::Enum state) { void NetworkDevice::disconnect() { if (this->bState == NetworkDeviceState::Disconnected) { - qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already disconnected"; + qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; return; } if (this->bState == NetworkDeviceState::Disconnecting) { - qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already disconnecting"; + qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting"; return; } - qCDebug(logNetworkNetworkDevice) << "Disconnecting from device" << this; + qCDebug(logNetworkDevice) << "Disconnecting from device" << this; signalDisconnect(); } +// WirelessNetworkDevice + +WirelessNetworkDevice::WirelessNetworkDevice(QObject* parent): NetworkDevice(parent) {}; + void WirelessNetworkDevice::scanComplete(qint64 lastScan) { this->bLastScan = lastScan; emit this->lastScanChanged(); @@ -86,28 +91,62 @@ void WirelessNetworkDevice::scanComplete(qint64 lastScan) { void WirelessNetworkDevice::scan() { if (this->bScanning) { - qCCritical(logNetworkNetworkDevice) << "NetworkDevice" << this << "is already scanning"; + qCCritical(logNetworkDevice) << "Wireless device" << this << "is already scanning"; return; } - qCDebug(logNetworkNetworkDevice) << "Requesting scan on wireless device" << this; + qCDebug(logNetworkDevice) << "Requesting scan on wireless device" << this; this->bScanning = true; signalScan(); } +void WirelessNetworkDevice::addAccessPoint(NetworkAccessPoint* ap) { + mAccessPoints.insertObject(ap); +} + +void WirelessNetworkDevice::removeAccessPoint(NetworkAccessPoint* ap) { + mAccessPoints.removeObject(ap); +} + +// NetworkAccessPoint + +NetworkAccessPoint::NetworkAccessPoint(QObject* parent): QObject(parent) {}; + +void NetworkAccessPoint::setSsid(const QString& ssid) { + if (this->bSsid != ssid) { + this->bSsid = ssid; + emit ssidChanged(); + } +} + +void NetworkAccessPoint::setSignal(quint8 signal) { + if (this->bSignal != signal) { + this->bSignal = signal; + emit signalChanged(); + } +} + +// Network + Network::Network(QObject* parent): QObject(parent) { // Try each backend // NetworkManager auto* nm = new NetworkManager(); if (nm->isAvailable()) { + QObject::connect(nm, &NetworkManager::deviceAdded, this, &Network::addDevice); + QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); this->backend = nm; return; } // None found this->backend = nullptr; - qCDebug(logNetwork) << "Network will not work. Could not find an available backend."; + qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; } +void Network::addDevice(NetworkDevice* device) { this->mDevices.insertObject(device); } + +void Network::removeDevice(NetworkDevice* device) { this->mDevices.removeObject(device); } + } // namespace qs::network diff --git a/src/network/api.hpp b/src/network/api.hpp index ef5d8f4c..63eb9628 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -14,6 +14,36 @@ namespace qs::network { +///! A tracked access point on the network +class NetworkAccessPoint: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE("WirelessNetwork can only be acquired through Network"); + // clang-format off + /// The service set identifier of the access point. + Q_PROPERTY(QString ssid READ default NOTIFY ssidChanged BINDABLE bindableSsid); + // The current signal quality of the access point, in percent. + Q_PROPERTY(quint8 signal READ default NOTIFY signalChanged BINDABLE bindableSignal); + //clang-format on + +signals: + void ssidChanged(); + void signalChanged(); + +public slots: + void setSsid(const QString& ssid); + void setSignal(quint8 signal); + +public: + explicit NetworkAccessPoint(QObject* parent = nullptr); + + [[nodiscard]] QBindable bindableSsid() const { return &this->bSsid; }; + [[nodiscard]] QBindable bindableSignal() const { return &this->bSignal; }; + +private: + Q_OBJECT_BINDABLE_PROPERTY(NetworkAccessPoint, QString, bSsid, &NetworkAccessPoint::ssidChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkAccessPoint, quint8, bSignal, &NetworkAccessPoint::signalChanged); +}; ///! Type of network device. class NetworkDeviceType: public QObject { @@ -79,11 +109,9 @@ class NetworkDevice: public QObject { void addressChanged(); void stateChanged(); - // For backend slots void signalDisconnect(); public slots: - // For backend signals void setName(const QString& name); void setAddress(const QString& address); void setState(NetworkDeviceState::Enum state); @@ -102,7 +130,12 @@ public slots: private: Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NetworkDeviceState::Enum, bState, &NetworkDevice::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY( + NetworkDevice, + NetworkDeviceState::Enum, + bState, + &NetworkDevice::stateChanged + ); }; ///! Wireless variant of a tracked network device. @@ -114,18 +147,21 @@ class WirelessNetworkDevice: public NetworkDevice { Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); /// True if the wireless device is currently scanning for available wifi networks. Q_PROPERTY(bool scanning READ default NOTIFY scanningChanged BINDABLE bindableScanning); + /// A list of all available access points + Q_PROPERTY(UntypedObjectModel* accessPoints READ accessPoints CONSTANT); + QSDOC_TYPE_OVERRIDE(ObjectModel*) //clang-format on signals: void signalScan(); - // Frontend-facing signals void lastScanChanged(); void scanningChanged(); public slots: - // For backend signals void scanComplete(qint64 lastScan); + void addAccessPoint(NetworkAccessPoint* ap); + void removeAccessPoint(NetworkAccessPoint* ap); public: explicit WirelessNetworkDevice(QObject* parent = nullptr); @@ -137,11 +173,12 @@ public slots: [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; }; [[nodiscard]] QBindable bindableLastScan() { return &this->bLastScan; }; + UntypedObjectModel* accessPoints() { return &this->mAccessPoints; }; + private: - // clang-format off + ObjectModel mAccessPoints{this}; Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, bool, bScanning, &WirelessNetworkDevice::scanningChanged); Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, qint64, bLastScan, &WirelessNetworkDevice::lastScanChanged); - // clang-format on }; // -- Network -- @@ -150,29 +187,36 @@ class NetworkBackend: public QObject { public: [[nodiscard]] virtual bool isAvailable() const = 0; - virtual UntypedObjectModel* devices() = 0; - virtual WirelessNetworkDevice* defaultWifiDevice() = 0; protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; }; +///! Network manager +/// Provides access to network devices. class Network: public QObject { Q_OBJECT; QML_NAMED_ELEMENT(Network); QML_SINGLETON; - Q_PROPERTY(WirelessNetworkDevice* defaultWifiNetworkDevice READ defaultWifiNetworkDevice CONSTANT); + // clang-format off + /// The default wifi device. Usually there is only one. This defaults to the first wifi device registered. + // Q_PROPERTY(WirelessNetworkDevice* defaultWifiDevice READ defaultWifiDevice CONSTANT); + /// A list of all network devices. Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); + QSDOC_TYPE_OVERRIDE(ObjectModel*); + // clang-format on + +public slots: + void addDevice(NetworkDevice* device); + void removeDevice(NetworkDevice* device); public: explicit Network(QObject* parent = nullptr); - [[nodiscard]] UntypedObjectModel* devices() { return backend ? backend->devices() : nullptr; }; - [[nodiscard]] WirelessNetworkDevice* defaultWifiNetworkDevice() { - return backend ? backend->defaultWifiDevice() : nullptr; - }; + [[nodiscard]] UntypedObjectModel* devices() { return backend ? &this->mDevices : nullptr; }; private: + ObjectModel mDevices {this}; class NetworkBackend* backend = nullptr; }; diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 5ab7cfe4..13c89532 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -9,16 +9,20 @@ #include #include "../dbus/properties.hpp" +#include "dbus_nm_accesspoint.h" #include "dbus_nm_device.h" +#include "dbus_nm_wireless.h" using namespace qs::dbus; namespace qs::network { namespace { -Q_LOGGING_CATEGORY(logNMDevice, "quickshell.network.networkmanager.device", QtWarningMsg); +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } +// Device + NMDeviceAdapter::NMDeviceAdapter(QObject* parent): QObject(parent) {} void NMDeviceAdapter::init(const QString& path) { @@ -30,7 +34,7 @@ void NMDeviceAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMDeviceAdapter for" << path; + qCWarning(logNetworkManager) << "Cannot create NMDeviceAdapter for" << path; return; } @@ -46,15 +50,17 @@ QString NMDeviceAdapter::address() const { QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } NetworkDeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { - switch(state) { - case 0 ... 20: return NetworkDeviceState::Unknown; - case 30: return NetworkDeviceState::Disconnected; - case 40 ... 90: return NetworkDeviceState::Connecting; - case 100: return NetworkDeviceState::Connected; - case 110 ... 120: return NetworkDeviceState::Disconnecting; + switch (state) { + case 0 ... 20: return NetworkDeviceState::Unknown; + case 30: return NetworkDeviceState::Disconnected; + case 40 ... 90: return NetworkDeviceState::Connecting; + case 100: return NetworkDeviceState::Connected; + case 110 ... 120: return NetworkDeviceState::Disconnecting; } } +// Wireless + NMWirelessAdapter::NMWirelessAdapter(QObject* parent): QObject(parent) {} void NMWirelessAdapter::init(const QString& path) { @@ -66,20 +72,138 @@ void NMWirelessAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNMDevice) << "Cannot create NMWirelessAdapter for" << path; + qCWarning(logNetworkManager) << "Cannot create NMWirelessAdapter for" << path; return; } + QObject::connect( + this->proxy, + &DBusNMWirelessProxy::AccessPointAdded, + this, + &NMWirelessAdapter::onAccessPointAdded + ); + + QObject::connect( + this->proxy, + &DBusNMWirelessProxy::AccessPointRemoved, + this, + &NMWirelessAdapter::onAccessPointRemoved + ); + this->wirelessProperties.setInterface(this->proxy); this->wirelessProperties.updateAllViaGetAll(); + + this->registerAccessPoints(); } -void NMWirelessAdapter::scan() { this->proxy->RequestScan(); } +void NMWirelessAdapter::onAccessPointAdded(const QDBusObjectPath& path) { + this->registerAccessPoint(path.path()); +} + +void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { + auto iter = this->mAPHash.find(path.path()); + if (iter == this->mAPHash.end()) { + qCWarning(logNetworkManager) << "NMWirelessAdapter sent removal signal for" << path.path() + << "which is not registered."; + } else { + auto* ap = iter.value(); + this->mAPHash.erase(iter); + emit accessPointRemoved(ap); + qCDebug(logNetworkManager) << "Access point" << path.path() << "removed."; + } +} + +void NMWirelessAdapter::registerAccessPoints() { + auto pending = this->proxy->GetAllAccessPoints(); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply> reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) << "Failed to get access points: " << reply.error().message(); + } else { + for (const QDBusObjectPath& devicePath: reply.value()) { + this->registerAccessPoint(devicePath.path()); + } + } + + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NMWirelessAdapter::registerAccessPoint(const QString& path) { + if (this->mAPHash.contains(path)) { + qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path; + return; + } + + // Create an access point adapter + auto* apAdapter = new NMAccessPointAdapter(); + apAdapter->init(path); + + if (!apAdapter->isValid()) { + qCWarning(logNetworkManager) << "Cannot create NMAccessPointAdapter for" << path; + delete apAdapter; + return; + } + auto* ap = new NetworkAccessPoint(this); + apAdapter->setParent(ap); + + // NMAccessPointAdapter signal -> NetworkAccessPoint slot + QObject::connect(apAdapter, &NMAccessPointAdapter::ssidChanged, ap, &NetworkAccessPoint::setSsid); + QObject::connect( + apAdapter, + &NMAccessPointAdapter::signalChanged, + ap, + &NetworkAccessPoint::setSignal + ); + + this->mAPHash.insert(path, ap); + emit accessPointAdded(ap); + qCDebug(logNetworkManager) << "Registered access point" << path; +} + +void NMWirelessAdapter::scan() { this->proxy->RequestScan({}); } bool NMWirelessAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMWirelessAdapter::address() const { return this->proxy ? this->proxy->service() : QString(); } QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +// Access Point + +namespace { +Q_LOGGING_CATEGORY(logNMAccessPoint, "quickshell.network.networkmanager.accesspoint", QtWarningMsg); +} + +NMAccessPointAdapter::NMAccessPointAdapter(QObject* parent): QObject(parent) {} + +void NMAccessPointAdapter::init(const QString& path) { + this->proxy = new DBusNMAccessPointProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNMAccessPoint) << "Cannot create NMWirelessAdapter for" << path; + return; + } + + this->accessPointProperties.setInterface(this->proxy); + this->accessPointProperties.updateAllViaGetAll(); +} + +bool NMAccessPointAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMAccessPointAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + } // namespace qs::network namespace qs::dbus { diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp index 170ffc22..cea6793c 100644 --- a/src/network/nm_adapters.hpp +++ b/src/network/nm_adapters.hpp @@ -12,6 +12,7 @@ #include "../dbus/properties.hpp" #include "api.hpp" +#include "dbus_nm_accesspoint.h" #include "dbus_nm_device.h" #include "dbus_nm_wireless.h" @@ -113,6 +114,7 @@ class NMDeviceAdapter: public QObject { public: explicit NMDeviceAdapter(QObject* parent = nullptr); void init(const QString& path); + [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; @@ -141,6 +143,7 @@ public slots: QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pType, bType, deviceProperties, "DeviceType"); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pState, bState, deviceProperties, "State"); + // clang-format on DBusNMDeviceProxy* proxy = nullptr; }; @@ -151,6 +154,10 @@ class NMWirelessAdapter: public QObject { public: explicit NMWirelessAdapter(QObject* parent = nullptr); void init(const QString& path); + void registerAccessPoint(const QString& path); + void registerAccessPoints(); + QHash mAPHash; + [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; @@ -161,13 +168,63 @@ public slots: signals: void lastScanChanged(qint64 lastScan); + void accessPointAdded(NetworkAccessPoint* ap); + void accessPointRemoved(NetworkAccessPoint* ap); + +private slots: + void onAccessPointAdded(const QDBusObjectPath& path); + void onAccessPointRemoved(const QDBusObjectPath& path); private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); - + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); + // clang-format on + DBusNMWirelessProxy* proxy = nullptr; }; + +class NMAccessPointAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMAccessPointAdapter(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + +signals: + void ssidChanged(QByteArray ssid); + void signalChanged(uchar signal); + +private: + //clang-format off + Q_OBJECT_BINDABLE_PROPERTY( + NMAccessPointAdapter, + QByteArray, + bSsid, + &NMAccessPointAdapter::ssidChanged + ); + Q_OBJECT_BINDABLE_PROPERTY( + NMAccessPointAdapter, + uchar, + bSignal, + &NMAccessPointAdapter::signalChanged + ); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); + QS_DBUS_PROPERTY_BINDING( + NMAccessPointAdapter, + pSignal, + bSignal, + accessPointProperties, + "Strength" + ); + + DBusNMAccessPointProxy* proxy = nullptr; +}; } // namespace qs::network diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index 2c53c7c5..22a84cb5 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -47,7 +47,7 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { this->init(); } else { qCWarning(logNetworkManager) - << "Could not start NetworkManager. The NetworkManager backend will not work."; + << "Could not start NetworkManager. This network backend will not work."; } }); } else { @@ -56,13 +56,13 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { } void NetworkManager::init() { + // Proxy signals -> NetworkManager slots QObject::connect( this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDeviceAdded ); - QObject::connect( this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, @@ -109,10 +109,11 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { if (!deviceAdapter->isValid()) { qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; + delete deviceAdapter; return; } - // Wait for DBus to tell us the device type + // Wait for DBus to send us the device type QObject::connect( deviceAdapter, &NMDeviceAdapter::typeChanged, @@ -130,9 +131,9 @@ void NetworkManager::registerDevice( const QString& path ) { NetworkDevice* device = createDeviceVariant(type, path); + deviceAdapter->setParent(device); // NMDeviceAdapter signal -> NetworkDevice slot - deviceAdapter->setParent(device); QObject::connect( deviceAdapter, &NMDeviceAdapter::hwAddressChanged, @@ -162,7 +163,7 @@ void NetworkManager::registerDevice( // Track device this->mDeviceHash.insert(path, device); - this->mDevices.insertObject(device); + emit deviceAdded(device); qCDebug(logNetworkManager) << "Registered device at path" << path; } @@ -175,22 +176,33 @@ NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, cons } } -// Create a frontend WirelessNetworkDevice and bind the backend wireless adapter to it +// Create a WirelessNetworkDevice and bind the NMWirelessAdapter WirelessNetworkDevice* NetworkManager::bindWirelessDevice(const QString& path) { auto* device = new WirelessNetworkDevice(this); auto* wirelessAdapter = new NMWirelessAdapter(device); wirelessAdapter->init(path); + // TODO: Check isValid() - throw error + // NMWirelessAdapter signal -> WirelessNetworkDevice slot QObject::connect( wirelessAdapter, &NMWirelessAdapter::lastScanChanged, device, - [device](qint64 lastScan) { - device->scanComplete(lastScan); - qCDebug(logNetworkManager) << lastScan; - } + &WirelessNetworkDevice::scanComplete + ); + QObject::connect( + wirelessAdapter, + &NMWirelessAdapter::accessPointAdded, + device, + &WirelessNetworkDevice::addAccessPoint + ); + QObject::connect( + wirelessAdapter, + &NMWirelessAdapter::accessPointRemoved, + device, + &WirelessNetworkDevice::removeAccessPoint ); // WirelessNetworkDevice signal -> NMWirelessAdapter slot @@ -201,9 +213,6 @@ WirelessNetworkDevice* NetworkManager::bindWirelessDevice(const QString& path) { &NMWirelessAdapter::scan ); - if (this->mWifi == nullptr) { - this->mWifi = device; - }; return device; } @@ -219,17 +228,12 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { << "which is not registered."; } else { auto* device = iter.value(); - if (this->mWifi == device) { - this->mWifi = nullptr; - }; this->mDeviceHash.erase(iter); - this->mDevices.removeObject(device); - qCDebug(logNetworkManager) << "NetworkDevice" << path.path() << "removed."; + emit deviceRemoved(device); + qCDebug(logNetworkManager) << "Device" << path.path() << "removed."; } } -UntypedObjectModel* NetworkManager::devices() { return &this->mDevices; } -WirelessNetworkDevice* NetworkManager::defaultWifiDevice() { return this->mWifi; } bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); } } // namespace qs::network diff --git a/src/network/nm_backend.hpp b/src/network/nm_backend.hpp index 3e96b3e3..5c1c5659 100644 --- a/src/network/nm_backend.hpp +++ b/src/network/nm_backend.hpp @@ -20,11 +20,12 @@ namespace qs::network { class NetworkManager: public NetworkBackend { Q_OBJECT; +signals: + void deviceAdded(NetworkDevice* device); + void deviceRemoved(NetworkDevice* device); + public: explicit NetworkManager(QObject* parent = nullptr); - - UntypedObjectModel* devices() override; - WirelessNetworkDevice* defaultWifiDevice() override; [[nodiscard]] bool isAvailable() const override; private slots: @@ -41,11 +42,8 @@ private slots: NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); QHash mDeviceHash; - ObjectModel mDevices {this}; - WirelessNetworkDevice* mWifi = nullptr; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); - DBusNetworkManagerProxy* proxy = nullptr; }; diff --git a/src/network/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/org.freedesktop.NetworkManager.AccessPoint.xml index 33872050..0f5dcf2f 100644 --- a/src/network/org.freedesktop.NetworkManager.AccessPoint.xml +++ b/src/network/org.freedesktop.NetworkManager.AccessPoint.xml @@ -1,11 +1,6 @@ - - - - - diff --git a/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml index 17b712bb..cc83004a 100644 --- a/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml +++ b/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml @@ -6,13 +6,16 @@ - + + + + - arg name="access_ponit" type="o"/> + diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 357daa54..08c5da7a 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -41,12 +41,31 @@ FloatingWindow { visible: modelData.state === NetworkDeviceState.Connected } } - RowLayout { - Label { text: "Last scan: " + modelData.lastScan } - Button { - text: "Scan" - onClicked: modelData.scan() - visible: modelData.scanning === false; + ColumnLayout { + RowLayout { + Label { text: "Last scan: " + modelData.lastScan } + Button { + text: "Scan" + onClicked: modelData.scan() + visible: modelData.scanning === false; + } + } + Label { text: "Available access points: " } + Repeater { + model: modelData.accessPoints + + delegate: WrapperRectangle { + height: apLabel.implicitHeight + 8 + color: "transparent" + border.color: palette.button + border.width: 1 + + Label { + id: apLabel + anchors.centerIn: parent + text: "SSID: " + (modelData.ssid || "[Hidden]") + ` SIGNAL: ${modelData.signal}` + } + } } visible: modelData.type === NetworkDeviceType.Wireless } From 70906334c20b93ce1d7b5926393504097d3f0b91 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 16 Jul 2025 17:09:39 -0500 Subject: [PATCH 25/32] fix: annotate RequestScan a{sv} for qdbusxml2cpp --- src/network/nm_adapters.cpp | 3 ++- src/network/org.freedesktop.NetworkManager.Device.Wireless.xml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 13c89532..1ce02f0c 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -104,7 +104,7 @@ void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { auto iter = this->mAPHash.find(path.path()); if (iter == this->mAPHash.end()) { qCWarning(logNetworkManager) << "NMWirelessAdapter sent removal signal for" << path.path() - << "which is not registered."; + << "which is not registered."; } else { auto* ap = iter.value(); this->mAPHash.erase(iter); @@ -167,6 +167,7 @@ void NMWirelessAdapter::registerAccessPoint(const QString& path) { } void NMWirelessAdapter::scan() { this->proxy->RequestScan({}); } + bool NMWirelessAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMWirelessAdapter::address() const { return this->proxy ? this->proxy->service() : QString(); diff --git a/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml index cc83004a..5814185d 100644 --- a/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml +++ b/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml @@ -8,7 +8,8 @@ - + + From 64299919acc45a5721d1a6166801b02f628818aa Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Sun, 20 Jul 2025 21:25:35 -0500 Subject: [PATCH 26/32] feat: AccessPoints -> WifiNetworks Access point objects shouldn't be provided to the user. Instead, we should expose WifiNetworks which represent a set of APs with the same SSID. A WifiNetwork signal represents the maximum signal of its APs. --- src/network/api.cpp | 20 +++-- src/network/api.hpp | 38 ++++----- src/network/nm_adapters.cpp | 148 +++++++++++++++++++++++++++-------- src/network/nm_adapters.hpp | 114 +++++++++++++++++---------- src/network/nm_backend.cpp | 22 +++--- src/network/nm_backend.hpp | 2 +- src/network/test/network.qml | 4 +- 7 files changed, 229 insertions(+), 119 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 2eb7a5ce..6e4184c8 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -77,9 +77,9 @@ void NetworkDevice::disconnect() { // WirelessNetworkDevice -WirelessNetworkDevice::WirelessNetworkDevice(QObject* parent): NetworkDevice(parent) {}; +NetworkWifiDevice::NetworkWifiDevice(QObject* parent): NetworkDevice(parent) {}; -void WirelessNetworkDevice::scanComplete(qint64 lastScan) { +void NetworkWifiDevice::scanComplete(qint64 lastScan) { this->bLastScan = lastScan; emit this->lastScanChanged(); @@ -89,7 +89,7 @@ void WirelessNetworkDevice::scanComplete(qint64 lastScan) { } } -void WirelessNetworkDevice::scan() { +void NetworkWifiDevice::scan() { if (this->bScanning) { qCCritical(logNetworkDevice) << "Wireless device" << this << "is already scanning"; return; @@ -100,26 +100,24 @@ void WirelessNetworkDevice::scan() { signalScan(); } -void WirelessNetworkDevice::addAccessPoint(NetworkAccessPoint* ap) { - mAccessPoints.insertObject(ap); -} +void NetworkWifiDevice::addNetwork(NetworkWifiNetwork* network) { mNetworks.insertObject(network); } -void WirelessNetworkDevice::removeAccessPoint(NetworkAccessPoint* ap) { - mAccessPoints.removeObject(ap); +void NetworkWifiDevice::removeNetwork(NetworkWifiNetwork* network) { + mNetworks.removeObject(network); } // NetworkAccessPoint -NetworkAccessPoint::NetworkAccessPoint(QObject* parent): QObject(parent) {}; +NetworkWifiNetwork::NetworkWifiNetwork(QObject* parent): QObject(parent) {}; -void NetworkAccessPoint::setSsid(const QString& ssid) { +void NetworkWifiNetwork::setSsid(const QString& ssid) { if (this->bSsid != ssid) { this->bSsid = ssid; emit ssidChanged(); } } -void NetworkAccessPoint::setSignal(quint8 signal) { +void NetworkWifiNetwork::setSignal(quint8 signal) { if (this->bSignal != signal) { this->bSignal = signal; emit signalChanged(); diff --git a/src/network/api.hpp b/src/network/api.hpp index 63eb9628..e9e478fc 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -14,15 +14,15 @@ namespace qs::network { -///! A tracked access point on the network -class NetworkAccessPoint: public QObject { +///! A wifi network available to a wifi device +class NetworkWifiNetwork: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("WirelessNetwork can only be acquired through Network"); + QML_UNCREATABLE("Wifi netorks can only be acquired through Network"); // clang-format off - /// The service set identifier of the access point. + /// The service set identifier of the wifi network Q_PROPERTY(QString ssid READ default NOTIFY ssidChanged BINDABLE bindableSsid); - // The current signal quality of the access point, in percent. + // The current signal quality of the best access point on the network, in percent. Q_PROPERTY(quint8 signal READ default NOTIFY signalChanged BINDABLE bindableSignal); //clang-format on @@ -35,14 +35,14 @@ public slots: void setSignal(quint8 signal); public: - explicit NetworkAccessPoint(QObject* parent = nullptr); + explicit NetworkWifiNetwork(QObject* parent = nullptr); [[nodiscard]] QBindable bindableSsid() const { return &this->bSsid; }; [[nodiscard]] QBindable bindableSignal() const { return &this->bSignal; }; private: - Q_OBJECT_BINDABLE_PROPERTY(NetworkAccessPoint, QString, bSsid, &NetworkAccessPoint::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkAccessPoint, quint8, bSignal, &NetworkAccessPoint::signalChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiNetwork, QString, bSsid, &NetworkWifiNetwork::ssidChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiNetwork, quint8, bSignal, &NetworkWifiNetwork::signalChanged); }; ///! Type of network device. @@ -139,7 +139,7 @@ public slots: }; ///! Wireless variant of a tracked network device. -class WirelessNetworkDevice: public NetworkDevice { +class NetworkWifiDevice: public NetworkDevice { Q_OBJECT; // clang-format off @@ -147,9 +147,9 @@ class WirelessNetworkDevice: public NetworkDevice { Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); /// True if the wireless device is currently scanning for available wifi networks. Q_PROPERTY(bool scanning READ default NOTIFY scanningChanged BINDABLE bindableScanning); - /// A list of all available access points - Q_PROPERTY(UntypedObjectModel* accessPoints READ accessPoints CONSTANT); - QSDOC_TYPE_OVERRIDE(ObjectModel*) + /// A list of all available wifi networks + Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); + QSDOC_TYPE_OVERRIDE(ObjectModel*) //clang-format on signals: @@ -160,11 +160,11 @@ class WirelessNetworkDevice: public NetworkDevice { public slots: void scanComplete(qint64 lastScan); - void addAccessPoint(NetworkAccessPoint* ap); - void removeAccessPoint(NetworkAccessPoint* ap); + void addNetwork(NetworkWifiNetwork* network); + void removeNetwork(NetworkWifiNetwork* network); public: - explicit WirelessNetworkDevice(QObject* parent = nullptr); + explicit NetworkWifiDevice(QObject* parent = nullptr); [[nodiscard]] NetworkDeviceType::Enum type() const override { return NetworkDeviceType::Wireless; }; /// Request the wireless device to scan for available WiFi networks. @@ -173,12 +173,12 @@ public slots: [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; }; [[nodiscard]] QBindable bindableLastScan() { return &this->bLastScan; }; - UntypedObjectModel* accessPoints() { return &this->mAccessPoints; }; + UntypedObjectModel* networks() { return &this->mNetworks; }; private: - ObjectModel mAccessPoints{this}; - Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, bool, bScanning, &WirelessNetworkDevice::scanningChanged); - Q_OBJECT_BINDABLE_PROPERTY(WirelessNetworkDevice, qint64, bLastScan, &WirelessNetworkDevice::lastScanChanged); + ObjectModel mNetworks {this}; + Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, bool, bScanning, &NetworkWifiDevice::scanningChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, qint64, bLastScan, &NetworkWifiDevice::lastScanChanged); }; // -- Network -- diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 1ce02f0c..8961cb94 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -34,7 +34,7 @@ void NMDeviceAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create NMDeviceAdapter for" << path; + qCWarning(logNetworkManager) << "Cannot create dbus proxy for" << path; return; } @@ -72,7 +72,7 @@ void NMWirelessAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create NMWirelessAdapter for" << path; + qCWarning(logNetworkManager) << "Cannot create dbus proxy for" << path; return; } @@ -101,16 +101,16 @@ void NMWirelessAdapter::onAccessPointAdded(const QDBusObjectPath& path) { } void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { - auto iter = this->mAPHash.find(path.path()); - if (iter == this->mAPHash.end()) { - qCWarning(logNetworkManager) << "NMWirelessAdapter sent removal signal for" << path.path() - << "which is not registered."; - } else { - auto* ap = iter.value(); - this->mAPHash.erase(iter); - emit accessPointRemoved(ap); - qCDebug(logNetworkManager) << "Access point" << path.path() << "removed."; + auto* ap = mPathToApHash.take(path.path()); + + if (!ap) { + qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path.path() + << "which is not registered."; + return; } + + removeApFromNetwork(ap); + delete ap; } void NMWirelessAdapter::registerAccessPoints() { @@ -135,35 +135,80 @@ void NMWirelessAdapter::registerAccessPoints() { } void NMWirelessAdapter::registerAccessPoint(const QString& path) { - if (this->mAPHash.contains(path)) { + if (this->mPathToApHash.contains(path)) { qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path; return; } - // Create an access point adapter - auto* apAdapter = new NMAccessPointAdapter(); - apAdapter->init(path); + auto* ap = new NMAccessPointAdapter(this); + ap->init(path); - if (!apAdapter->isValid()) { - qCWarning(logNetworkManager) << "Cannot create NMAccessPointAdapter for" << path; - delete apAdapter; + if (!ap->isValid()) { + qCWarning(logNetworkManager) << "Cannot create access point for" << path; + delete ap; return; } - auto* ap = new NetworkAccessPoint(this); - apAdapter->setParent(ap); - // NMAccessPointAdapter signal -> NetworkAccessPoint slot - QObject::connect(apAdapter, &NMAccessPointAdapter::ssidChanged, ap, &NetworkAccessPoint::setSsid); + this->mPathToApHash.insert(path, ap); + qCDebug(logNetworkManager) << "Registered access point" << path; + QObject::connect( - apAdapter, - &NMAccessPointAdapter::signalChanged, ap, - &NetworkAccessPoint::setSignal + &NMAccessPointAdapter::ssidChanged, + this, + [this, ap](const QByteArray& ssid) { + if (ssid.isEmpty()) { + this->removeApFromNetwork(ap); + } else { + this->addApToNetwork(ap, ssid); + } + } ); +} - this->mAPHash.insert(path, ap); - emit accessPointAdded(ap); - qCDebug(logNetworkManager) << "Registered access point" << path; +void NMWirelessAdapter::addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid) { + // Remove AP from old network + removeApFromNetwork(ap); + + auto* group = mSsidToApGroupHash.value(ssid); + if (!group) { + // Create a new AP group and wifi network + group = new NMAccessPointGroup(ssid, this); + auto* network = new NetworkWifiNetwork(this); + network->setSsid(ssid); + + // NMAccessPointGroup signal -> NetworkWifiNetwork slot + QObject::connect( + group, + &NMAccessPointGroup::maxSignalChanged, + network, + &NetworkWifiNetwork::setSignal + ); + + this->mSsidToApGroupHash.insert(ssid, group); + this->mSsidToNetworkHash.insert(ssid, network); + emit this->wifiNetworkAdded(network); + qCDebug(logNetworkManager) << "Registered wifi network" << ssid; + } + + group->addAccessPoint(ap); + this->mPathToSsidHash.insert(ap->path(), ssid); +} + +void NMWirelessAdapter::removeApFromNetwork(NMAccessPointAdapter* ap) { + QByteArray ssid = mPathToSsidHash.take(ap->path()); + if (ssid.isEmpty()) return; // AP wasn't in any network + + auto* group = mSsidToApGroupHash.value(ssid); + group->removeAccessPoint(ap); + if (group->isEmpty()) { + mSsidToApGroupHash.remove(ssid); + auto* network = mSsidToNetworkHash.take(ssid); + emit wifiNetworkRemoved(network); + delete network; + delete group; + qCDebug(logNetworkManager) << "Deleted wifi network" << ssid; + } } void NMWirelessAdapter::scan() { this->proxy->RequestScan({}); } @@ -176,10 +221,6 @@ QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path // Access Point -namespace { -Q_LOGGING_CATEGORY(logNMAccessPoint, "quickshell.network.networkmanager.accesspoint", QtWarningMsg); -} - NMAccessPointAdapter::NMAccessPointAdapter(QObject* parent): QObject(parent) {} void NMAccessPointAdapter::init(const QString& path) { @@ -191,7 +232,7 @@ void NMAccessPointAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNMAccessPoint) << "Cannot create NMWirelessAdapter for" << path; + qCWarning(logNetworkManager) << "Cannot create access point proxy for" << path; return; } @@ -205,6 +246,47 @@ QString NMAccessPointAdapter::address() const { } QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } +// Access Point Group + +NMAccessPointGroup::NMAccessPointGroup(const QByteArray& ssid, QObject* parent) + : QObject(parent) + , mSsid(ssid) {} + +void NMAccessPointGroup::updateMaxSignal() { + quint8 max = 0; + for (auto* ap: mAccessPoints) { + max = qMax(max, ap->getSignal()); + } + if (this->bMaxSignal != max) { + this->bMaxSignal = max; + } +} + +void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { + if (this->mAccessPoints.contains(ap)) return; + + this->mAccessPoints.append(ap); + QObject::connect( + ap, + &NMAccessPointAdapter::signalChanged, + this, + &NMAccessPointGroup::updateMaxSignal + ); + this->updateMaxSignal(); +} + +void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { + if (mAccessPoints.removeOne(ap)) { + QObject::disconnect( + ap, + &NMAccessPointAdapter::signalChanged, + this, + &NMAccessPointGroup::updateMaxSignal + ); + this->updateMaxSignal(); + } +} + } // namespace qs::network namespace qs::dbus { diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp index cea6793c..86390d3d 100644 --- a/src/network/nm_adapters.hpp +++ b/src/network/nm_adapters.hpp @@ -108,6 +108,39 @@ struct DBusDataTransform { namespace qs::network { +// NMAccessPointAdapter wraps the state of a NetworkManager access point +// (org.freedesktop.NetworkManager.AccessPoint) +class NMAccessPointAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMAccessPointAdapter(QObject* parent = nullptr); + void init(const QString& path); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] quint8 getSignal() const { return this->bSignal; }; + +signals: + void ssidChanged(const QByteArray& ssid); + void signalChanged(quint8 signal); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, QByteArray, bSsid, &NMAccessPointAdapter::ssidChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignal, &NMAccessPointAdapter::signalChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignal, bSignal, accessPointProperties, "Strength"); + // clang-format on + + DBusNMAccessPointProxy* proxy = nullptr; +}; + +// NMDeviceAdapter wraps the state of a NetworkManager device +// (org.freedesktop.NetworkManager.Device) and provides signals/slots +// that connect to a frontend NetworkDevice. class NMDeviceAdapter: public QObject { Q_OBJECT; @@ -148,6 +181,34 @@ public slots: DBusNMDeviceProxy* proxy = nullptr; }; +// NMAccessPointGroup bundles the state of access points with the same SSID +// into a single object and supplies signals/slots to connect AP signal strength +// to NetworkWifiNetwork signal strength +class NMAccessPointGroup: public QObject { + Q_OBJECT; + +public: + explicit NMAccessPointGroup(const QByteArray& ssid, QObject* parent = nullptr); + void addAccessPoint(NMAccessPointAdapter* ap); + void removeAccessPoint(NMAccessPointAdapter* ap); + void updateMaxSignal(); + bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; + +signals: + void maxSignalChanged(quint8 signal); + +private: + QList mAccessPoints; + QByteArray mSsid; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::maxSignalChanged); + // clang-format on +}; + +// NMWirelessAdapter wraps the state of a NetworkManager wireless device +// (org.freedesktop.NetworkManager.Device.Wireless), provides signals/slots that connect to a +// frontend NetworkWifiDevice, and creates/destroys NMAccessPointAdapters and NMAccessPointGroups class NMWirelessAdapter: public QObject { Q_OBJECT; @@ -156,7 +217,8 @@ class NMWirelessAdapter: public QObject { void init(const QString& path); void registerAccessPoint(const QString& path); void registerAccessPoints(); - QHash mAPHash; + void addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid); + void removeApFromNetwork(NMAccessPointAdapter* ap); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; @@ -168,14 +230,20 @@ public slots: signals: void lastScanChanged(qint64 lastScan); - void accessPointAdded(NetworkAccessPoint* ap); - void accessPointRemoved(NetworkAccessPoint* ap); + void wifiNetworkAdded(NetworkWifiNetwork* network); + void wifiNetworkRemoved(NetworkWifiNetwork* network); private slots: void onAccessPointAdded(const QDBusObjectPath& path); void onAccessPointRemoved(const QDBusObjectPath& path); private: + // Allows for efficient lookups (AP <-> AP Group <-> Wifi Network) + QHash mPathToApHash; // AP Path -> NMAccessPointAdapter* + QHash mSsidToApGroupHash; // Ssid -> NMAccessPointGroup* + QHash mPathToSsidHash; // AP Path -> Ssid* + QHash mSsidToNetworkHash; // Ssid -> NetworkWifiNetwork* + // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); @@ -186,45 +254,5 @@ private slots: DBusNMWirelessProxy* proxy = nullptr; }; -class NMAccessPointAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMAccessPointAdapter(QObject* parent = nullptr); - void init(const QString& path); - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - -signals: - void ssidChanged(QByteArray ssid); - void signalChanged(uchar signal); -private: - //clang-format off - Q_OBJECT_BINDABLE_PROPERTY( - NMAccessPointAdapter, - QByteArray, - bSsid, - &NMAccessPointAdapter::ssidChanged - ); - Q_OBJECT_BINDABLE_PROPERTY( - NMAccessPointAdapter, - uchar, - bSignal, - &NMAccessPointAdapter::signalChanged - ); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); - QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); - QS_DBUS_PROPERTY_BINDING( - NMAccessPointAdapter, - pSignal, - bSignal, - accessPointProperties, - "Strength" - ); - - DBusNMAccessPointProxy* proxy = nullptr; -}; } // namespace qs::network diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index 22a84cb5..bda06372 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -113,14 +113,15 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { return; } - // Wait for DBus to send us the device type + // Wait for DBus to send the device type QObject::connect( deviceAdapter, &NMDeviceAdapter::typeChanged, this, [this, deviceAdapter, path](NMDeviceType::Enum type) { this->registerDevice(deviceAdapter, type, path); - } + }, + Qt::SingleShotConnection ); } @@ -177,8 +178,8 @@ NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, cons } // Create a WirelessNetworkDevice and bind the NMWirelessAdapter -WirelessNetworkDevice* NetworkManager::bindWirelessDevice(const QString& path) { - auto* device = new WirelessNetworkDevice(this); +NetworkWifiDevice* NetworkManager::bindWirelessDevice(const QString& path) { + auto* device = new NetworkWifiDevice(this); auto* wirelessAdapter = new NMWirelessAdapter(device); wirelessAdapter->init(path); @@ -190,25 +191,25 @@ WirelessNetworkDevice* NetworkManager::bindWirelessDevice(const QString& path) { wirelessAdapter, &NMWirelessAdapter::lastScanChanged, device, - &WirelessNetworkDevice::scanComplete + &NetworkWifiDevice::scanComplete ); QObject::connect( wirelessAdapter, - &NMWirelessAdapter::accessPointAdded, + &NMWirelessAdapter::wifiNetworkAdded, device, - &WirelessNetworkDevice::addAccessPoint + &NetworkWifiDevice::addNetwork ); QObject::connect( wirelessAdapter, - &NMWirelessAdapter::accessPointRemoved, + &NMWirelessAdapter::wifiNetworkRemoved, device, - &WirelessNetworkDevice::removeAccessPoint + &NetworkWifiDevice::removeNetwork ); // WirelessNetworkDevice signal -> NMWirelessAdapter slot QObject::connect( device, - &WirelessNetworkDevice::signalScan, + &NetworkWifiDevice::signalScan, wirelessAdapter, &NMWirelessAdapter::scan ); @@ -230,6 +231,7 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { auto* device = iter.value(); this->mDeviceHash.erase(iter); emit deviceRemoved(device); + delete device; qCDebug(logNetworkManager) << "Device" << path.path() << "removed."; } } diff --git a/src/network/nm_backend.hpp b/src/network/nm_backend.hpp index 5c1c5659..a0f4e37b 100644 --- a/src/network/nm_backend.hpp +++ b/src/network/nm_backend.hpp @@ -38,7 +38,7 @@ private slots: void registerDevices(); void queueDeviceRegistration(const QString& path); NetworkDevice* createDeviceVariant(NMDeviceType::Enum type, const QString& path); - WirelessNetworkDevice* bindWirelessDevice(const QString& path); + NetworkWifiDevice* bindWirelessDevice(const QString& path); NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); QHash mDeviceHash; diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 08c5da7a..586be60f 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -50,9 +50,9 @@ FloatingWindow { visible: modelData.scanning === false; } } - Label { text: "Available access points: " } + Label { text: "Available networks: " } Repeater { - model: modelData.accessPoints + model: modelData.networks delegate: WrapperRectangle { height: apLabel.implicitHeight + 8 From 61f8c8ffdfa216160eaeb8921c5a898d9e5894c8 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Mon, 21 Jul 2025 04:01:11 -0500 Subject: [PATCH 27/32] feat: connected --- src/network/api.cpp | 39 ++++++----- src/network/api.hpp | 60 ++++++++-------- src/network/nm_adapters.cpp | 131 +++++++++++++++++++---------------- src/network/nm_adapters.hpp | 52 +++++++------- src/network/nm_backend.cpp | 23 +++--- src/network/test/network.qml | 56 +++++++++++---- 6 files changed, 208 insertions(+), 153 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 6e4184c8..5ef0204f 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -17,8 +17,6 @@ Q_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); Q_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } // namespace -// NetworkDevice - NetworkDevice::NetworkDevice(QObject* parent): QObject(parent) {}; QString NetworkDeviceState::toString(NetworkDeviceState::Enum state) { @@ -36,7 +34,6 @@ QString NetworkDeviceType::toString(NetworkDeviceType::Enum type) { switch (type) { case NetworkDeviceType::Other: return QStringLiteral("Other"); case NetworkDeviceType::Wireless: return QStringLiteral("Wireless"); - case NetworkDeviceType::Ethernet: return QStringLiteral("Ethernet"); default: return QStringLiteral("Unknown"); } } @@ -75,8 +72,6 @@ void NetworkDevice::disconnect() { signalDisconnect(); } -// WirelessNetworkDevice - NetworkWifiDevice::NetworkWifiDevice(QObject* parent): NetworkDevice(parent) {}; void NetworkWifiDevice::scanComplete(qint64 lastScan) { @@ -100,31 +95,38 @@ void NetworkWifiDevice::scan() { signalScan(); } -void NetworkWifiDevice::addNetwork(NetworkWifiNetwork* network) { mNetworks.insertObject(network); } +void NetworkWifiDevice::addNetwork(WifiNetwork* network) { mNetworks.insertObjectSorted(network, &NetworkWifiDevice::compareNetworks); } -void NetworkWifiDevice::removeNetwork(NetworkWifiNetwork* network) { - mNetworks.removeObject(network); +bool NetworkWifiDevice::compareNetworks(WifiNetwork* a, WifiNetwork* b) { + return a->bindableSignalStrength().value() > b->bindableSignalStrength().value(); } -// NetworkAccessPoint +void NetworkWifiDevice::removeNetwork(WifiNetwork* network) { + mNetworks.removeObject(network); +} -NetworkWifiNetwork::NetworkWifiNetwork(QObject* parent): QObject(parent) {}; +WifiNetwork::WifiNetwork(QObject* parent): QObject(parent) {}; -void NetworkWifiNetwork::setSsid(const QString& ssid) { +void WifiNetwork::setSsid(const QString& ssid) { if (this->bSsid != ssid) { this->bSsid = ssid; - emit ssidChanged(); + emit this->ssidChanged(); } } -void NetworkWifiNetwork::setSignal(quint8 signal) { - if (this->bSignal != signal) { - this->bSignal = signal; - emit signalChanged(); +void WifiNetwork::setSignalStrength(quint8 signal) { + if (this->bSignalStrength != signal) { + this->bSignalStrength = signal; + emit this->signalStrengthChanged(); } } -// Network +void WifiNetwork::setConnected(bool connected) { + if (this->bConnected != connected) { + this->bConnected = connected; + emit this->connectedChanged(); + } +} Network::Network(QObject* parent): QObject(parent) { // Try each backend @@ -136,10 +138,11 @@ Network::Network(QObject* parent): QObject(parent) { QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); this->backend = nm; return; + } else { + delete nm; } // None found - this->backend = nullptr; qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; } diff --git a/src/network/api.hpp b/src/network/api.hpp index e9e478fc..1604c2b4 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -15,34 +15,42 @@ namespace qs::network { ///! A wifi network available to a wifi device -class NetworkWifiNetwork: public QObject { +class WifiNetwork: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("Wifi netorks can only be acquired through Network"); + QML_UNCREATABLE("Wifi networks can only be acquired through Network"); // clang-format off /// The service set identifier of the wifi network Q_PROPERTY(QString ssid READ default NOTIFY ssidChanged BINDABLE bindableSsid); - // The current signal quality of the best access point on the network, in percent. - Q_PROPERTY(quint8 signal READ default NOTIFY signalChanged BINDABLE bindableSignal); - //clang-format on + // The current signal strength of the best access point on the network, in percent. + Q_PROPERTY(quint8 signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength); + /// True if the wireless device is curerntly connected to this wifi network. + Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); + // clang-format on signals: void ssidChanged(); - void signalChanged(); + void signalStrengthChanged(); + void connectedChanged(); public slots: void setSsid(const QString& ssid); - void setSignal(quint8 signal); + void setSignalStrength(quint8 signalStrength); + void setConnected(bool connected); public: - explicit NetworkWifiNetwork(QObject* parent = nullptr); + explicit WifiNetwork(QObject* parent = nullptr); [[nodiscard]] QBindable bindableSsid() const { return &this->bSsid; }; - [[nodiscard]] QBindable bindableSignal() const { return &this->bSignal; }; + [[nodiscard]] QBindable bindableSignalStrength() const { return &this->bSignalStrength; }; + [[nodiscard]] QBindable bindableConnected() const { return &this->bConnected; }; private: - Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiNetwork, QString, bSsid, &NetworkWifiNetwork::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiNetwork, quint8, bSignal, &NetworkWifiNetwork::signalChanged); + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, QString, bSsid, &WifiNetwork::ssidChanged); + Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, quint8, bSignalStrength, &WifiNetwork::signalStrengthChanged); + Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bConnected, &WifiNetwork::connectedChanged); + // clang-format on }; ///! Type of network device. @@ -57,8 +65,6 @@ class NetworkDeviceType: public QObject { Other = 0, ///! An 802.11 Wi-Fi device. Wireless = 1, - ///! A wired ethernet device. - Ethernet = 2 }; Q_ENUM(Enum); Q_INVOKABLE static QString toString(NetworkDeviceType::Enum type); @@ -91,7 +97,7 @@ class NetworkDeviceState: public QObject { class NetworkDevice: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("NetworkDevices can only be acquired through Network"); + QML_UNCREATABLE("Devices can only be acquired through Network"); // clang-format off /// The name of the device's interface. @@ -128,14 +134,11 @@ public slots: [[nodiscard]] QBindable bindableState() const { return &this->bState; }; private: + // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged); - Q_OBJECT_BINDABLE_PROPERTY( - NetworkDevice, - NetworkDeviceState::Enum, - bState, - &NetworkDevice::stateChanged - ); + Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NetworkDeviceState::Enum, bState, &NetworkDevice::stateChanged); + // clang-format on }; ///! Wireless variant of a tracked network device. @@ -145,11 +148,13 @@ class NetworkWifiDevice: public NetworkDevice { // clang-format off /// The timestamp (in CLOCK_BOOTTIME milliseconds) for the last finished network scan. Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); - /// True if the wireless device is currently scanning for available wifi networks. + /// True if the wifi device is currently scanning for available wifi networks. Q_PROPERTY(bool scanning READ default NOTIFY scanningChanged BINDABLE bindableScanning); - /// A list of all available wifi networks + /// The currently active wifi network + // Q_PROPERTY(WifiNetwork activeNetwork READ networks CONSTANT); + /// A list of all wifi networks available to this wifi device Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); - QSDOC_TYPE_OVERRIDE(ObjectModel*) + QSDOC_TYPE_OVERRIDE(ObjectModel*) //clang-format on signals: @@ -160,8 +165,8 @@ class NetworkWifiDevice: public NetworkDevice { public slots: void scanComplete(qint64 lastScan); - void addNetwork(NetworkWifiNetwork* network); - void removeNetwork(NetworkWifiNetwork* network); + void addNetwork(WifiNetwork* network); + void removeNetwork(WifiNetwork* network); public: explicit NetworkWifiDevice(QObject* parent = nullptr); @@ -176,12 +181,13 @@ public slots: UntypedObjectModel* networks() { return &this->mNetworks; }; private: - ObjectModel mNetworks {this}; + static bool compareNetworks(WifiNetwork* a, WifiNetwork* b); + ObjectModel mNetworks {this}; + Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, bool, bScanning, &NetworkWifiDevice::scanningChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, qint64, bLastScan, &NetworkWifiDevice::lastScanChanged); }; -// -- Network -- class NetworkBackend: public QObject { Q_OBJECT; diff --git a/src/network/nm_adapters.cpp b/src/network/nm_adapters.cpp index 8961cb94..523cbec6 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm_adapters.cpp @@ -21,11 +21,7 @@ namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } -// Device - -NMDeviceAdapter::NMDeviceAdapter(QObject* parent): QObject(parent) {} - -void NMDeviceAdapter::init(const QString& path) { +NMDeviceAdapter::NMDeviceAdapter(const QString& path, QObject* parent): QObject(parent) { this->proxy = new DBusNMDeviceProxy( "org.freedesktop.NetworkManager", path, @@ -34,7 +30,7 @@ void NMDeviceAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create dbus proxy for" << path; + qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path; return; } @@ -49,7 +45,7 @@ QString NMDeviceAdapter::address() const { } QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NetworkDeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { +NetworkDeviceState::Enum NMDeviceState::toNetworkDeviceState(NMDeviceState::Enum state) { switch (state) { case 0 ... 20: return NetworkDeviceState::Unknown; case 30: return NetworkDeviceState::Disconnected; @@ -59,11 +55,7 @@ NetworkDeviceState::Enum NMDeviceState::translate(NMDeviceState::Enum state) { } } -// Wireless - -NMWirelessAdapter::NMWirelessAdapter(QObject* parent): QObject(parent) {} - -void NMWirelessAdapter::init(const QString& path) { +NMWirelessAdapter::NMWirelessAdapter(const QString& path, QObject* parent): QObject(parent) { this->proxy = new DBusNMWirelessProxy( "org.freedesktop.NetworkManager", path, @@ -72,7 +64,7 @@ void NMWirelessAdapter::init(const QString& path) { ); if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create dbus proxy for" << path; + qCWarning(logNetworkManager) << "Cannot create DBus interface for wireless device at" << path; return; } @@ -90,6 +82,13 @@ void NMWirelessAdapter::init(const QString& path) { &NMWirelessAdapter::onAccessPointRemoved ); + QObject::connect( + this, + &NMWirelessAdapter::activeApChanged, + this, + &NMWirelessAdapter::onActiveApChanged + ); + this->wirelessProperties.setInterface(this->proxy); this->wirelessProperties.updateAllViaGetAll(); @@ -101,7 +100,7 @@ void NMWirelessAdapter::onAccessPointAdded(const QDBusObjectPath& path) { } void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { - auto* ap = mPathToApHash.take(path.path()); + auto* ap = mApMap.take(path.path()); if (!ap) { qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path.path() @@ -113,6 +112,24 @@ void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { delete ap; } +void NMWirelessAdapter::onActiveApChanged(const QDBusObjectPath& path) { + // Disconnect all + for (WifiNetwork* nw: mWifiNetworkMap) { + if (nw) { + nw->setConnected(false); + }; + } + + QByteArray ssid = this->mSsidMap.value(path.path()); + if (ssid != "") { + WifiNetwork* network = this->mWifiNetworkMap.value(ssid); + if (network) { + network->setConnected(true); + return; + } + } +} + void NMWirelessAdapter::registerAccessPoints() { auto pending = this->proxy->GetAllAccessPoints(); auto* call = new QDBusPendingCallWatcher(pending, this); @@ -121,7 +138,8 @@ void NMWirelessAdapter::registerAccessPoints() { const QDBusPendingReply> reply = *call; if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to get access points: " << reply.error().message(); + qCWarning(logNetworkManager) + << "Failed to get all access points: " << reply.error().message(); } else { for (const QDBusObjectPath& devicePath: reply.value()) { this->registerAccessPoint(devicePath.path()); @@ -135,29 +153,29 @@ void NMWirelessAdapter::registerAccessPoints() { } void NMWirelessAdapter::registerAccessPoint(const QString& path) { - if (this->mPathToApHash.contains(path)) { + if (this->mApMap.contains(path)) { qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path; return; } - auto* ap = new NMAccessPointAdapter(this); - ap->init(path); + auto* ap = new NMAccessPointAdapter(path, this); if (!ap->isValid()) { - qCWarning(logNetworkManager) << "Cannot create access point for" << path; + qCWarning(logNetworkManager) << "Could not create DBus interface for access point at" << path; delete ap; return; } - this->mPathToApHash.insert(path, ap); + this->mApMap.insert(path, ap); qCDebug(logNetworkManager) << "Registered access point" << path; + // AP SSID can change QObject::connect( ap, &NMAccessPointAdapter::ssidChanged, this, [this, ap](const QByteArray& ssid) { - if (ssid.isEmpty()) { + if (ssid.isEmpty()) { // AP SSID can be hidden this->removeApFromNetwork(ap); } else { this->addApToNetwork(ap, ssid); @@ -170,44 +188,49 @@ void NMWirelessAdapter::addApToNetwork(NMAccessPointAdapter* ap, const QByteArra // Remove AP from old network removeApFromNetwork(ap); - auto* group = mSsidToApGroupHash.value(ssid); + auto* group = mApGroupMap.value(ssid); if (!group) { - // Create a new AP group and wifi network + auto* network = new WifiNetwork(this); group = new NMAccessPointGroup(ssid, this); - auto* network = new NetworkWifiNetwork(this); - network->setSsid(ssid); + network->setSsid(QString::fromUtf8(ssid)); // NMAccessPointGroup signal -> NetworkWifiNetwork slot QObject::connect( group, - &NMAccessPointGroup::maxSignalChanged, + &NMAccessPointGroup::signalStrengthChanged, network, - &NetworkWifiNetwork::setSignal + &WifiNetwork::setSignalStrength ); - this->mSsidToApGroupHash.insert(ssid, group); - this->mSsidToNetworkHash.insert(ssid, network); + this->mApGroupMap.insert(ssid, group); + this->mWifiNetworkMap.insert(ssid, network); + + // Sometimes active AP changes before wifi network is registered + if (this->activeApPath().path() == ap->path()) { + network->setConnected(true); + }; + emit this->wifiNetworkAdded(network); - qCDebug(logNetworkManager) << "Registered wifi network" << ssid; } group->addAccessPoint(ap); - this->mPathToSsidHash.insert(ap->path(), ssid); + this->mSsidMap.insert(ap->path(), ssid); } void NMWirelessAdapter::removeApFromNetwork(NMAccessPointAdapter* ap) { - QByteArray ssid = mPathToSsidHash.take(ap->path()); - if (ssid.isEmpty()) return; // AP wasn't in any network + QByteArray ssid = mSsidMap.take(ap->path()); + if (ssid.isEmpty()) return; // AP wasn't previously associated with a wifi network - auto* group = mSsidToApGroupHash.value(ssid); + auto* group = mApGroupMap.value(ssid); group->removeAccessPoint(ap); + + // No access points exist for the wifi network if (group->isEmpty()) { - mSsidToApGroupHash.remove(ssid); - auto* network = mSsidToNetworkHash.take(ssid); + mApGroupMap.remove(ssid); + auto* network = mWifiNetworkMap.take(ssid); emit wifiNetworkRemoved(network); delete network; delete group; - qCDebug(logNetworkManager) << "Deleted wifi network" << ssid; } } @@ -219,11 +242,7 @@ QString NMWirelessAdapter::address() const { } QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -// Access Point - -NMAccessPointAdapter::NMAccessPointAdapter(QObject* parent): QObject(parent) {} - -void NMAccessPointAdapter::init(const QString& path) { +NMAccessPointAdapter::NMAccessPointAdapter(const QString& path, QObject* parent): QObject(parent) { this->proxy = new DBusNMAccessPointProxy( "org.freedesktop.NetworkManager", path, @@ -246,13 +265,11 @@ QString NMAccessPointAdapter::address() const { } QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -// Access Point Group - -NMAccessPointGroup::NMAccessPointGroup(const QByteArray& ssid, QObject* parent) +NMAccessPointGroup::NMAccessPointGroup(QByteArray ssid, QObject* parent) : QObject(parent) - , mSsid(ssid) {} + , mSsid(std::move(ssid)) {} -void NMAccessPointGroup::updateMaxSignal() { +void NMAccessPointGroup::updateSignalStrength() { quint8 max = 0; for (auto* ap: mAccessPoints) { max = qMax(max, ap->getSignal()); @@ -263,27 +280,25 @@ void NMAccessPointGroup::updateMaxSignal() { } void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { - if (this->mAccessPoints.contains(ap)) return; + if (this->mAccessPoints.contains(ap)) { + qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in AP group"; + return; + } this->mAccessPoints.append(ap); QObject::connect( ap, - &NMAccessPointAdapter::signalChanged, + &NMAccessPointAdapter::signalStrengthChanged, this, - &NMAccessPointGroup::updateMaxSignal + &NMAccessPointGroup::updateSignalStrength ); - this->updateMaxSignal(); + this->updateSignalStrength(); } void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { if (mAccessPoints.removeOne(ap)) { - QObject::disconnect( - ap, - &NMAccessPointAdapter::signalChanged, - this, - &NMAccessPointGroup::updateMaxSignal - ); - this->updateMaxSignal(); + QObject::disconnect(ap, nullptr, this, nullptr); + this->updateSignalStrength(); } } diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp index 86390d3d..3b6fade0 100644 --- a/src/network/nm_adapters.hpp +++ b/src/network/nm_adapters.hpp @@ -39,7 +39,7 @@ class NMDeviceState: public QObject { }; Q_ENUM(Enum); - static NetworkDeviceState::Enum translate(NMDeviceState::Enum state); + static NetworkDeviceState::Enum toNetworkDeviceState(NMDeviceState::Enum state); }; class NMDeviceType: public QObject { @@ -114,25 +114,25 @@ class NMAccessPointAdapter: public QObject { Q_OBJECT; public: - explicit NMAccessPointAdapter(QObject* parent = nullptr); - void init(const QString& path); + explicit NMAccessPointAdapter(const QString& path, QObject* parent = nullptr); + [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; - [[nodiscard]] quint8 getSignal() const { return this->bSignal; }; + [[nodiscard]] quint8 getSignal() const { return this->bSignalStrength; }; signals: void ssidChanged(const QByteArray& ssid); - void signalChanged(quint8 signal); + void signalStrengthChanged(quint8 signal); private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, QByteArray, bSsid, &NMAccessPointAdapter::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignal, &NMAccessPointAdapter::signalChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignalStrength, &NMAccessPointAdapter::signalStrengthChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); - QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignal, bSignal, accessPointProperties, "Strength"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); // clang-format on DBusNMAccessPointProxy* proxy = nullptr; @@ -145,8 +145,7 @@ class NMDeviceAdapter: public QObject { Q_OBJECT; public: - explicit NMDeviceAdapter(QObject* parent = nullptr); - void init(const QString& path); + explicit NMDeviceAdapter(const QString& path, QObject* parent = nullptr); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; @@ -188,21 +187,21 @@ class NMAccessPointGroup: public QObject { Q_OBJECT; public: - explicit NMAccessPointGroup(const QByteArray& ssid, QObject* parent = nullptr); + explicit NMAccessPointGroup(QByteArray ssid, QObject* parent = nullptr); void addAccessPoint(NMAccessPointAdapter* ap); void removeAccessPoint(NMAccessPointAdapter* ap); - void updateMaxSignal(); - bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; + void updateSignalStrength(); + [[nodiscard]] bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; signals: - void maxSignalChanged(quint8 signal); + void signalStrengthChanged(quint8 signal); private: QList mAccessPoints; QByteArray mSsid; // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::maxSignalChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::signalStrengthChanged); // clang-format on }; @@ -213,8 +212,8 @@ class NMWirelessAdapter: public QObject { Q_OBJECT; public: - explicit NMWirelessAdapter(QObject* parent = nullptr); - void init(const QString& path); + explicit NMWirelessAdapter(const QString& path, QObject* parent = nullptr); + void registerAccessPoint(const QString& path); void registerAccessPoints(); void addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid); @@ -224,35 +223,40 @@ class NMWirelessAdapter: public QObject { [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; + [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }; public slots: void scan(); signals: void lastScanChanged(qint64 lastScan); - void wifiNetworkAdded(NetworkWifiNetwork* network); - void wifiNetworkRemoved(NetworkWifiNetwork* network); + void activeApChanged(const QDBusObjectPath& path); + + void wifiNetworkAdded(WifiNetwork* network); + void wifiNetworkRemoved(WifiNetwork* network); private slots: void onAccessPointAdded(const QDBusObjectPath& path); void onAccessPointRemoved(const QDBusObjectPath& path); + void onActiveApChanged(const QDBusObjectPath& path); private: - // Allows for efficient lookups (AP <-> AP Group <-> Wifi Network) - QHash mPathToApHash; // AP Path -> NMAccessPointAdapter* - QHash mSsidToApGroupHash; // Ssid -> NMAccessPointGroup* - QHash mPathToSsidHash; // AP Path -> Ssid* - QHash mSsidToNetworkHash; // Ssid -> NetworkWifiNetwork* + // Lookups: AP <-> AP group <-> frontend wifi network + QHash mApMap; // AP Path -> NMAccessPointAdapter* + QHash mSsidMap; // AP Path -> Ssid + QHash mApGroupMap; // Ssid -> NMAccessPointGroup* + QHash mWifiNetworkMap; // Ssid -> NetworkWifiNetwork* // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, QDBusObjectPath, bActiveAccessPoint, &NMWirelessAdapter::activeApChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); + QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); // clang-format on DBusNMWirelessProxy* proxy = nullptr; }; - } // namespace qs::network diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index bda06372..4332d436 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -56,7 +56,6 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { } void NetworkManager::init() { - // Proxy signals -> NetworkManager slots QObject::connect( this->proxy, &DBusNetworkManagerProxy::DeviceAdded, @@ -103,9 +102,7 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { return; } - // Create a device adapter - auto* deviceAdapter = new NMDeviceAdapter(); - deviceAdapter->init(path); + auto* deviceAdapter = new NMDeviceAdapter(path); if (!deviceAdapter->isValid()) { qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; @@ -113,7 +110,7 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { return; } - // Wait for DBus to send the device type + // Wait to receive NMDeviceType before registering device QObject::connect( deviceAdapter, &NMDeviceAdapter::typeChanged, @@ -125,7 +122,6 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { ); } -// Register the device void NetworkManager::registerDevice( NMDeviceAdapter* deviceAdapter, NMDeviceType::Enum type, @@ -151,7 +147,9 @@ void NetworkManager::registerDevice( deviceAdapter, &NMDeviceAdapter::stateChanged, device, - [device](NMDeviceState::Enum state) { device->setState(NMDeviceState::translate(state)); } + [device](NMDeviceState::Enum state) { + device->setState(NMDeviceState::toNetworkDeviceState(state)); + } ); // NetworkDevice signal -> NMDeviceAdapter slot @@ -162,14 +160,13 @@ void NetworkManager::registerDevice( &NMDeviceAdapter::disconnect ); - // Track device this->mDeviceHash.insert(path, device); emit deviceAdded(device); - qCDebug(logNetworkManager) << "Registered device at path" << path; + qCDebug(logNetworkManager) << "Registered device" << path; } -// Create a derived device class based on the NMDeviceType of the NMDeviceAdapter +// Create a device derived from NMDeviceType NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, const QString& path) { switch (type) { case NMDeviceType::Wifi: return this->bindWirelessDevice(path); @@ -177,12 +174,10 @@ NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, cons } } -// Create a WirelessNetworkDevice and bind the NMWirelessAdapter +// Create a WirelessNetworkDevice and connect the NMWirelessAdapter NetworkWifiDevice* NetworkManager::bindWirelessDevice(const QString& path) { auto* device = new NetworkWifiDevice(this); - - auto* wirelessAdapter = new NMWirelessAdapter(device); - wirelessAdapter->init(path); + auto* wirelessAdapter = new NMWirelessAdapter(path, device); // TODO: Check isValid() - throw error diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 586be60f..29b15b87 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -29,6 +29,7 @@ FloatingWindow { Label { text: `Device ${index}: ${modelData.name}` font.bold: true + font.pointSize: 12 } Label { text: "Hardware Address: " + modelData.address } Label { text: "Device type: " + NetworkDeviceType.toString(modelData.type) } @@ -51,19 +52,50 @@ FloatingWindow { } } Label { text: "Available networks: " } - Repeater { - model: modelData.networks - delegate: WrapperRectangle { - height: apLabel.implicitHeight + 8 - color: "transparent" - border.color: palette.button - border.width: 1 - - Label { - id: apLabel - anchors.centerIn: parent - text: "SSID: " + (modelData.ssid || "[Hidden]") + ` SIGNAL: ${modelData.signal}` + GridLayout { + columns: 3 + columnSpacing: 30 + rowSpacing: 5 + + Label { + Layout.row: 0 + Layout.column: 0 + text: "SSID" + } + Label { + Layout.row: 0 + Layout.column: 1 + text: "SIGNAL" + } + Label { + Layout.row: 0 + Layout.column: 2 + text: "CONNECTED" + } + + Repeater { + model: modelData.networks + delegate: Text { + Layout.row: index + 1 + Layout.column: 0 + text: modelData.ssid || "[Hidden]" + } + } + Repeater { + model: modelData.networks + delegate: Text { + Layout.row: index + 1 + Layout.column: 1 + text: `${modelData.signalStrength}%` + } + } + Repeater { + model: modelData.networks + delegate: Text { + Layout.row: index + 1 + Layout.column: 2 + text: modelData.connected ? "*" : "" } } } From 8b2113797552935c9f3f0cf270cd8fda343fa694 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Tue, 22 Jul 2025 15:27:19 -0500 Subject: [PATCH 28/32] feat: remove lastScan LastScan is still relevant to the NetworkManager backend to set the scanning state to false (completed), but doesn't need to be exposed to the frontend and isn't available in other backends --- src/network/api.cpp | 19 +++++-------------- src/network/api.hpp | 22 +++++++--------------- src/network/nm_backend.cpp | 4 ++-- src/network/test/network.qml | 1 - 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/src/network/api.cpp b/src/network/api.cpp index 5ef0204f..a9d9267b 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -69,15 +69,12 @@ void NetworkDevice::disconnect() { qCDebug(logNetworkDevice) << "Disconnecting from device" << this; - signalDisconnect(); + this->requestDisconnect(); } NetworkWifiDevice::NetworkWifiDevice(QObject* parent): NetworkDevice(parent) {}; -void NetworkWifiDevice::scanComplete(qint64 lastScan) { - this->bLastScan = lastScan; - emit this->lastScanChanged(); - +void NetworkWifiDevice::scanComplete() { if (this->bScanning) { this->bScanning = false; emit this->scanningChanged(); @@ -92,18 +89,12 @@ void NetworkWifiDevice::scan() { qCDebug(logNetworkDevice) << "Requesting scan on wireless device" << this; this->bScanning = true; - signalScan(); + this->requestScan(); } -void NetworkWifiDevice::addNetwork(WifiNetwork* network) { mNetworks.insertObjectSorted(network, &NetworkWifiDevice::compareNetworks); } - -bool NetworkWifiDevice::compareNetworks(WifiNetwork* a, WifiNetwork* b) { - return a->bindableSignalStrength().value() > b->bindableSignalStrength().value(); -} +void NetworkWifiDevice::addNetwork(WifiNetwork* network) { mNetworks.insertObject(network); } -void NetworkWifiDevice::removeNetwork(WifiNetwork* network) { - mNetworks.removeObject(network); -} +void NetworkWifiDevice::removeNetwork(WifiNetwork* network) { mNetworks.removeObject(network); } WifiNetwork::WifiNetwork(QObject* parent): QObject(parent) {}; diff --git a/src/network/api.hpp b/src/network/api.hpp index 1604c2b4..6f047747 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -14,7 +14,7 @@ namespace qs::network { -///! A wifi network available to a wifi device +///! A wifi network class WifiNetwork: public QObject { Q_OBJECT; QML_ELEMENT; @@ -53,7 +53,7 @@ public slots: // clang-format on }; -///! Type of network device. +///! Type of Network device. class NetworkDeviceType: public QObject { Q_OBJECT; QML_ELEMENT; @@ -70,7 +70,7 @@ class NetworkDeviceType: public QObject { Q_INVOKABLE static QString toString(NetworkDeviceType::Enum type); }; -///! State of a network device. +///! Connection state of a Network device. class NetworkDeviceState: public QObject { Q_OBJECT; QML_ELEMENT; @@ -93,7 +93,7 @@ class NetworkDeviceState: public QObject { Q_INVOKABLE static QString toString(NetworkDeviceState::Enum state); }; -///! A tracked network device. +///! A tracked Network device. class NetworkDevice: public QObject { Q_OBJECT; QML_ELEMENT; @@ -114,8 +114,7 @@ class NetworkDevice: public QObject { void nameChanged(); void addressChanged(); void stateChanged(); - - void signalDisconnect(); + void requestDisconnect(); public slots: void setName(const QString& name); @@ -146,8 +145,6 @@ class NetworkWifiDevice: public NetworkDevice { Q_OBJECT; // clang-format off - /// The timestamp (in CLOCK_BOOTTIME milliseconds) for the last finished network scan. - Q_PROPERTY(qint64 lastScan READ default NOTIFY lastScanChanged BINDABLE bindableLastScan); /// True if the wifi device is currently scanning for available wifi networks. Q_PROPERTY(bool scanning READ default NOTIFY scanningChanged BINDABLE bindableScanning); /// The currently active wifi network @@ -158,13 +155,11 @@ class NetworkWifiDevice: public NetworkDevice { //clang-format on signals: - void signalScan(); - - void lastScanChanged(); + void requestScan(); void scanningChanged(); public slots: - void scanComplete(qint64 lastScan); + void scanComplete(); void addNetwork(WifiNetwork* network); void removeNetwork(WifiNetwork* network); @@ -176,16 +171,13 @@ public slots: Q_INVOKABLE void scan(); [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; }; - [[nodiscard]] QBindable bindableLastScan() { return &this->bLastScan; }; UntypedObjectModel* networks() { return &this->mNetworks; }; private: - static bool compareNetworks(WifiNetwork* a, WifiNetwork* b); ObjectModel mNetworks {this}; Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, bool, bScanning, &NetworkWifiDevice::scanningChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkWifiDevice, qint64, bLastScan, &NetworkWifiDevice::lastScanChanged); }; class NetworkBackend: public QObject { diff --git a/src/network/nm_backend.cpp b/src/network/nm_backend.cpp index 4332d436..29471a24 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm_backend.cpp @@ -155,7 +155,7 @@ void NetworkManager::registerDevice( // NetworkDevice signal -> NMDeviceAdapter slot QObject::connect( device, - &NetworkDevice::signalDisconnect, + &NetworkDevice::requestDisconnect, deviceAdapter, &NMDeviceAdapter::disconnect ); @@ -204,7 +204,7 @@ NetworkWifiDevice* NetworkManager::bindWirelessDevice(const QString& path) { // WirelessNetworkDevice signal -> NMWirelessAdapter slot QObject::connect( device, - &NetworkWifiDevice::signalScan, + &NetworkWifiDevice::requestScan, wirelessAdapter, &NMWirelessAdapter::scan ); diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 29b15b87..4de9391f 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -44,7 +44,6 @@ FloatingWindow { } ColumnLayout { RowLayout { - Label { text: "Last scan: " + modelData.lastScan } Button { text: "Scan" onClicked: modelData.scan() From c9558112b3ca747e050a502081c757615bdf2a91 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 23 Jul 2025 11:38:10 -0500 Subject: [PATCH 29/32] refactor: move nm_* to nm/ and seperate adapter classes --- src/network/CMakeLists.txt | 30 +- src/network/api.cpp | 2 +- src/network/nm/accesspoint.cpp | 82 ++++++ src/network/nm/accesspoint.hpp | 73 +++++ .../{nm_backend.cpp => nm/backend.cpp} | 11 +- .../{nm_backend.hpp => nm/backend.hpp} | 6 +- src/network/nm/device.cpp | 70 +++++ src/network/nm/device.hpp | 151 ++++++++++ ...freedesktop.NetworkManager.AccessPoint.xml | 0 ...desktop.NetworkManager.Device.Wireless.xml | 0 .../org.freedesktop.NetworkManager.Device.xml | 0 .../org.freedesktop.NetworkManager.xml | 0 .../{nm_adapters.cpp => nm/wireless.cpp} | 116 +------- src/network/nm/wireless.hpp | 74 +++++ src/network/nm_adapters.hpp | 262 ------------------ 15 files changed, 479 insertions(+), 398 deletions(-) create mode 100644 src/network/nm/accesspoint.cpp create mode 100644 src/network/nm/accesspoint.hpp rename src/network/{nm_backend.cpp => nm/backend.cpp} (97%) rename src/network/{nm_backend.hpp => nm/backend.hpp} (94%) create mode 100644 src/network/nm/device.cpp create mode 100644 src/network/nm/device.hpp rename src/network/{ => nm}/org.freedesktop.NetworkManager.AccessPoint.xml (100%) rename src/network/{ => nm}/org.freedesktop.NetworkManager.Device.Wireless.xml (100%) rename src/network/{ => nm}/org.freedesktop.NetworkManager.Device.xml (100%) rename src/network/{ => nm}/org.freedesktop.NetworkManager.xml (100%) rename src/network/{nm_adapters.cpp => nm/wireless.cpp} (60%) create mode 100644 src/network/nm/wireless.hpp delete mode 100644 src/network/nm_adapters.hpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 1de97dd2..f82aee9d 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,48 +1,50 @@ # NetworkManager DBus -set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES +set_source_files_properties(nm/org.freedesktop.NetworkManager.xml PROPERTIES CLASSNAME DBusNetworkManagerProxy NO_NAMESPACE TRUE ) qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.xml - dbus_nm_backend + nm/org.freedesktop.NetworkManager.xml + nm/dbus_nm_backend ) -set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES +set_source_files_properties(nm/org.freedesktop.NetworkManager.Device.xml PROPERTIES CLASSNAME DBusNMDeviceProxy NO_NAMESPACE TRUE ) qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.xml - dbus_nm_device + nm/org.freedesktop.NetworkManager.Device.xml + nm/dbus_nm_device ) -set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES +set_source_files_properties(nm/org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES CLASSNAME DBusNMWirelessProxy NO_NAMESPACE TRUE ) qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.Wireless.xml - dbus_nm_wireless + nm/org.freedesktop.NetworkManager.Device.Wireless.xml + nm/dbus_nm_wireless ) -set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES +set_source_files_properties(nm/org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES CLASSNAME DBusNMAccessPointProxy NO_NAMESPACE TRUE ) qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.AccessPoint.xml - dbus_nm_accesspoint + nm/org.freedesktop.NetworkManager.AccessPoint.xml + nm/dbus_nm_accesspoint ) qt_add_library(quickshell-network STATIC api.cpp - nm_backend.cpp - nm_adapters.cpp + nm/backend.cpp + nm/device.cpp + nm/accesspoint.cpp + nm/wireless.cpp ${NM_DBUS_INTERFACES} ) diff --git a/src/network/api.cpp b/src/network/api.cpp index a9d9267b..a255d599 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -8,7 +8,7 @@ #include #include -#include "nm_backend.hpp" +#include "nm/backend.hpp" namespace qs::network { diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp new file mode 100644 index 00000000..1eb27e76 --- /dev/null +++ b/src/network/nm/accesspoint.cpp @@ -0,0 +1,82 @@ +#include "accesspoint.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_nm_accesspoint.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); +} + +NMAccessPointAdapter::NMAccessPointAdapter(const QString& path, QObject* parent): QObject(parent) { + this->proxy = new DBusNMAccessPointProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNetworkManager) << "Cannot create access point proxy for" << path; + return; + } + + this->accessPointProperties.setInterface(this->proxy); + this->accessPointProperties.updateAllViaGetAll(); +} + +bool NMAccessPointAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMAccessPointAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +NMAccessPointGroup::NMAccessPointGroup(QByteArray ssid, QObject* parent) + : QObject(parent) + , mSsid(std::move(ssid)) {} + +void NMAccessPointGroup::updateSignalStrength() { + quint8 max = 0; + for (auto* ap: mAccessPoints) { + max = qMax(max, ap->getSignal()); + } + if (this->bMaxSignal != max) { + this->bMaxSignal = max; + } +} + +void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { + if (this->mAccessPoints.contains(ap)) { + qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in AP group"; + return; + } + + this->mAccessPoints.append(ap); + QObject::connect( + ap, + &NMAccessPointAdapter::signalStrengthChanged, + this, + &NMAccessPointGroup::updateSignalStrength + ); + this->updateSignalStrength(); +} + +void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { + if (mAccessPoints.removeOne(ap)) { + QObject::disconnect(ap, nullptr, this, nullptr); + this->updateSignalStrength(); + } +} + +} // namespace qs::network diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp new file mode 100644 index 00000000..785148c8 --- /dev/null +++ b/src/network/nm/accesspoint.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_nm_accesspoint.h" + +namespace qs::network { + +// NMAccessPointAdapter wraps the state of a NetworkManager access point +// (org.freedesktop.NetworkManager.AccessPoint) +class NMAccessPointAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMAccessPointAdapter(const QString& path, QObject* parent = nullptr); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] quint8 getSignal() const { return this->bSignalStrength; }; + +signals: + void ssidChanged(const QByteArray& ssid); + void signalStrengthChanged(quint8 signal); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, QByteArray, bSsid, &NMAccessPointAdapter::ssidChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignalStrength, &NMAccessPointAdapter::signalStrengthChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); + // clang-format on + + DBusNMAccessPointProxy* proxy = nullptr; +}; + +// NMAccessPointGroup bundles the state of access points with the same SSID +// into a single object and supplies signals/slots to connect AP signal strength +// to NetworkWifiNetwork signal strength +class NMAccessPointGroup: public QObject { + Q_OBJECT; + +public: + explicit NMAccessPointGroup(QByteArray ssid, QObject* parent = nullptr); + void addAccessPoint(NMAccessPointAdapter* ap); + void removeAccessPoint(NMAccessPointAdapter* ap); + void updateSignalStrength(); + [[nodiscard]] bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; + +signals: + void signalStrengthChanged(quint8 signal); + +private: + QList mAccessPoints; + QByteArray mSsid; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::signalStrengthChanged); + // clang-format on +}; + +} // namespace qs::network diff --git a/src/network/nm_backend.cpp b/src/network/nm/backend.cpp similarity index 97% rename from src/network/nm_backend.cpp rename to src/network/nm/backend.cpp index 29471a24..74da3274 100644 --- a/src/network/nm_backend.cpp +++ b/src/network/nm/backend.cpp @@ -1,4 +1,4 @@ -#include "nm_backend.hpp" +#include "backend.hpp" #include #include @@ -10,11 +10,12 @@ #include #include -#include "../dbus/bus.hpp" -#include "../dbus/properties.hpp" -#include "api.hpp" +#include "../../dbus/bus.hpp" +#include "../../dbus/properties.hpp" +#include "../api.hpp" +#include "device.hpp" +#include "wireless.hpp" #include "dbus_nm_backend.h" -#include "nm_adapters.hpp" namespace qs::network { diff --git a/src/network/nm_backend.hpp b/src/network/nm/backend.hpp similarity index 94% rename from src/network/nm_backend.hpp rename to src/network/nm/backend.hpp index a0f4e37b..23139e7f 100644 --- a/src/network/nm_backend.hpp +++ b/src/network/nm/backend.hpp @@ -10,10 +10,10 @@ #include #include -#include "../dbus/properties.hpp" -#include "api.hpp" +#include "../../dbus/properties.hpp" +#include "../api.hpp" #include "dbus_nm_backend.h" -#include "nm_adapters.hpp" +#include "device.hpp" namespace qs::network { diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp new file mode 100644 index 00000000..590e435a --- /dev/null +++ b/src/network/nm/device.cpp @@ -0,0 +1,70 @@ +#include "device.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "dbus_nm_device.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); +} + +NMDeviceAdapter::NMDeviceAdapter(const QString& path, QObject* parent): QObject(parent) { + this->proxy = new DBusNMDeviceProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path; + return; + } + + this->deviceProperties.setInterface(this->proxy); + this->deviceProperties.updateAllViaGetAll(); +} + +void NMDeviceAdapter::disconnect() { this->proxy->Disconnect(); } +bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMDeviceAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +NetworkDeviceState::Enum NMDeviceState::toNetworkDeviceState(NMDeviceState::Enum state) { + switch (state) { + case 0 ... 20: return NetworkDeviceState::Unknown; + case 30: return NetworkDeviceState::Disconnected; + case 40 ... 90: return NetworkDeviceState::Connecting; + case 100: return NetworkDeviceState::Connected; + case 110 ... 120: return NetworkDeviceState::Disconnecting; + } +} + +} // namespace qs::network + +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp new file mode 100644 index 00000000..0df47166 --- /dev/null +++ b/src/network/nm/device.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "api.hpp" +#include "dbus_nm_device.h" + +namespace qs::network { + +class NMDeviceState: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unkown = 0, + Unmanaged = 10, + Unavailable = 20, + Disconnected = 30, + Prepare = 40, + Config = 50, + NeedAuth = 60, + IPConfig = 70, + IPCheck = 80, + Secondaries = 90, + Activated = 100, + Deactivating = 110, + Failed = 120, + }; + Q_ENUM(Enum); + + static NetworkDeviceState::Enum toNetworkDeviceState(NMDeviceState::Enum state); +}; + +class NMDeviceType: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unknown = 0, + Generic = 14, + Ethernet = 1, + Wifi = 2, + Unused1 = 3, + Unused2 = 4, + Bluetooth = 5, + OlpcMesh = 6, + Wimax = 7, + Modem = 8, + Infiniband = 9, + Bond = 10, + Vlan = 11, + Adsl = 12, + Bridge = 13, + Team = 15, + Tun = 16, + Tunnel = 17, + Macvlan = 18, + Vxlan = 19, + Veth = 20, + Macsec = 21, + Dummy = 22, + Ppp = 23, + OvsInterface = 24, + OvsPort = 25, + OvsBridge = 26, + Wpan = 27, + Lowpan = 28, + WireGuard = 29, + WifiP2P = 30, + Vrf = 31, + Loopback = 32, + Hsr = 33, + Ipvlan = 34, + }; + Q_ENUM(Enum); +}; + +} // namespace qs::network + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMDeviceType::Enum; + static DBusResult fromWire(Wire wire); +}; + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMDeviceState::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::network { + +// NMDeviceAdapter wraps the state of a NetworkManager device +// (org.freedesktop.NetworkManager.Device) and provides signals/slots +// that connect to a frontend NetworkDevice. +class NMDeviceAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMDeviceAdapter(const QString& path, QObject* parent = nullptr); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] QString getInterface() { return this->bInterface; }; + [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; + [[nodiscard]] NMDeviceType::Enum getType() { return this->bType; }; + +public slots: + void disconnect(); + +signals: + void interfaceChanged(const QString& interface); + void hwAddressChanged(const QString& hwAddress); + void typeChanged(NMDeviceType::Enum type); + void stateChanged(NMDeviceState::Enum state); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceState::Enum, bState, &NMDeviceAdapter::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceType::Enum, bType, &NMDeviceAdapter::typeChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pType, bType, deviceProperties, "DeviceType"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pState, bState, deviceProperties, "State"); + // clang-format on + + DBusNMDeviceProxy* proxy = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml similarity index 100% rename from src/network/org.freedesktop.NetworkManager.AccessPoint.xml rename to src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml diff --git a/src/network/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml similarity index 100% rename from src/network/org.freedesktop.NetworkManager.Device.Wireless.xml rename to src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml diff --git a/src/network/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml similarity index 100% rename from src/network/org.freedesktop.NetworkManager.Device.xml rename to src/network/nm/org.freedesktop.NetworkManager.Device.xml diff --git a/src/network/org.freedesktop.NetworkManager.xml b/src/network/nm/org.freedesktop.NetworkManager.xml similarity index 100% rename from src/network/org.freedesktop.NetworkManager.xml rename to src/network/nm/org.freedesktop.NetworkManager.xml diff --git a/src/network/nm_adapters.cpp b/src/network/nm/wireless.cpp similarity index 60% rename from src/network/nm_adapters.cpp rename to src/network/nm/wireless.cpp index 523cbec6..3eb27746 100644 --- a/src/network/nm_adapters.cpp +++ b/src/network/nm/wireless.cpp @@ -1,4 +1,4 @@ -#include "nm_adapters.hpp" +#include "wireless.hpp" #include #include @@ -8,9 +8,7 @@ #include #include -#include "../dbus/properties.hpp" -#include "dbus_nm_accesspoint.h" -#include "dbus_nm_device.h" +#include "../../dbus/properties.hpp" #include "dbus_nm_wireless.h" using namespace qs::dbus; @@ -21,40 +19,6 @@ namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } -NMDeviceAdapter::NMDeviceAdapter(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path; - return; - } - - this->deviceProperties.setInterface(this->proxy); - this->deviceProperties.updateAllViaGetAll(); -} - -void NMDeviceAdapter::disconnect() { this->proxy->Disconnect(); } -bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMDeviceAdapter::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } - -NetworkDeviceState::Enum NMDeviceState::toNetworkDeviceState(NMDeviceState::Enum state) { - switch (state) { - case 0 ... 20: return NetworkDeviceState::Unknown; - case 30: return NetworkDeviceState::Disconnected; - case 40 ... 90: return NetworkDeviceState::Connecting; - case 100: return NetworkDeviceState::Connected; - case 110 ... 120: return NetworkDeviceState::Disconnecting; - } -} - NMWirelessAdapter::NMWirelessAdapter(const QString& path, QObject* parent): QObject(parent) { this->proxy = new DBusNMWirelessProxy( "org.freedesktop.NetworkManager", @@ -207,7 +171,7 @@ void NMWirelessAdapter::addApToNetwork(NMAccessPointAdapter* ap, const QByteArra // Sometimes active AP changes before wifi network is registered if (this->activeApPath().path() == ap->path()) { - network->setConnected(true); + network->setConnected(true); }; emit this->wifiNetworkAdded(network); @@ -242,78 +206,4 @@ QString NMWirelessAdapter::address() const { } QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NMAccessPointAdapter::NMAccessPointAdapter(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMAccessPointProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create access point proxy for" << path; - return; - } - - this->accessPointProperties.setInterface(this->proxy); - this->accessPointProperties.updateAllViaGetAll(); -} - -bool NMAccessPointAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMAccessPointAdapter::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } - -NMAccessPointGroup::NMAccessPointGroup(QByteArray ssid, QObject* parent) - : QObject(parent) - , mSsid(std::move(ssid)) {} - -void NMAccessPointGroup::updateSignalStrength() { - quint8 max = 0; - for (auto* ap: mAccessPoints) { - max = qMax(max, ap->getSignal()); - } - if (this->bMaxSignal != max) { - this->bMaxSignal = max; - } -} - -void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { - if (this->mAccessPoints.contains(ap)) { - qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in AP group"; - return; - } - - this->mAccessPoints.append(ap); - QObject::connect( - ap, - &NMAccessPointAdapter::signalStrengthChanged, - this, - &NMAccessPointGroup::updateSignalStrength - ); - this->updateSignalStrength(); -} - -void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { - if (mAccessPoints.removeOne(ap)) { - QObject::disconnect(ap, nullptr, this, nullptr); - this->updateSignalStrength(); - } -} - } // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp new file mode 100644 index 00000000..5779862f --- /dev/null +++ b/src/network/nm/wireless.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "../api.hpp" +#include "accesspoint.hpp" +#include "dbus_nm_wireless.h" + +namespace qs::network { + +// NMWirelessAdapter wraps the state of a NetworkManager wireless device +// (org.freedesktop.NetworkManager.Device.Wireless), provides signals/slots that connect to a +// frontend NetworkWifiDevice, and creates/destroys NMAccessPointAdapters and NMAccessPointGroups +class NMWirelessAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMWirelessAdapter(const QString& path, QObject* parent = nullptr); + + void registerAccessPoint(const QString& path); + void registerAccessPoints(); + void addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid); + void removeApFromNetwork(NMAccessPointAdapter* ap); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; + [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }; + +public slots: + void scan(); + +signals: + void lastScanChanged(qint64 lastScan); + void activeApChanged(const QDBusObjectPath& path); + + void wifiNetworkAdded(WifiNetwork* network); + void wifiNetworkRemoved(WifiNetwork* network); + +private slots: + void onAccessPointAdded(const QDBusObjectPath& path); + void onAccessPointRemoved(const QDBusObjectPath& path); + void onActiveApChanged(const QDBusObjectPath& path); + +private: + // Lookups: AP <-> AP group <-> frontend wifi network + QHash mApMap; // AP Path -> NMAccessPointAdapter* + QHash mSsidMap; // AP Path -> Ssid + QHash mApGroupMap; // Ssid -> NMAccessPointGroup* + QHash mWifiNetworkMap; // Ssid -> NetworkWifiNetwork* + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, QDBusObjectPath, bActiveAccessPoint, &NMWirelessAdapter::activeApChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); + QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); + QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); + // clang-format on + + DBusNMWirelessProxy* proxy = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/nm_adapters.hpp b/src/network/nm_adapters.hpp deleted file mode 100644 index 3b6fade0..00000000 --- a/src/network/nm_adapters.hpp +++ /dev/null @@ -1,262 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../dbus/properties.hpp" -#include "api.hpp" -#include "dbus_nm_accesspoint.h" -#include "dbus_nm_device.h" -#include "dbus_nm_wireless.h" - -namespace qs::network { - -class NMDeviceState: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unkown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); - - static NetworkDeviceState::Enum toNetworkDeviceState(NMDeviceState::Enum state); -}; - -class NMDeviceType: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Generic = 14, - Ethernet = 1, - Wifi = 2, - Unused1 = 3, - Unused2 = 4, - Bluetooth = 5, - OlpcMesh = 6, - Wimax = 7, - Modem = 8, - Infiniband = 9, - Bond = 10, - Vlan = 11, - Adsl = 12, - Bridge = 13, - Team = 15, - Tun = 16, - Tunnel = 17, - Macvlan = 18, - Vxlan = 19, - Veth = 20, - Macsec = 21, - Dummy = 22, - Ppp = 23, - OvsInterface = 24, - OvsPort = 25, - OvsBridge = 26, - Wpan = 27, - Lowpan = 28, - WireGuard = 29, - WifiP2P = 30, - Vrf = 31, - Loopback = 32, - Hsr = 33, - Ipvlan = 34, - }; - Q_ENUM(Enum); -}; - -} // namespace qs::network - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMDeviceType::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMDeviceState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -// NMAccessPointAdapter wraps the state of a NetworkManager access point -// (org.freedesktop.NetworkManager.AccessPoint) -class NMAccessPointAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMAccessPointAdapter(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] quint8 getSignal() const { return this->bSignalStrength; }; - -signals: - void ssidChanged(const QByteArray& ssid); - void signalStrengthChanged(quint8 signal); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, QByteArray, bSsid, &NMAccessPointAdapter::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignalStrength, &NMAccessPointAdapter::signalStrengthChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); - QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); - QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); - // clang-format on - - DBusNMAccessPointProxy* proxy = nullptr; -}; - -// NMDeviceAdapter wraps the state of a NetworkManager device -// (org.freedesktop.NetworkManager.Device) and provides signals/slots -// that connect to a frontend NetworkDevice. -class NMDeviceAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMDeviceAdapter(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QString getInterface() { return this->bInterface; }; - [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; - [[nodiscard]] NMDeviceType::Enum getType() { return this->bType; }; - -public slots: - void disconnect(); - -signals: - void interfaceChanged(const QString& interface); - void hwAddressChanged(const QString& hwAddress); - void typeChanged(NMDeviceType::Enum type); - void stateChanged(NMDeviceState::Enum state); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceState::Enum, bState, &NMDeviceAdapter::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceType::Enum, bType, &NMDeviceAdapter::typeChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pType, bType, deviceProperties, "DeviceType"); - QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pState, bState, deviceProperties, "State"); - // clang-format on - - DBusNMDeviceProxy* proxy = nullptr; -}; - -// NMAccessPointGroup bundles the state of access points with the same SSID -// into a single object and supplies signals/slots to connect AP signal strength -// to NetworkWifiNetwork signal strength -class NMAccessPointGroup: public QObject { - Q_OBJECT; - -public: - explicit NMAccessPointGroup(QByteArray ssid, QObject* parent = nullptr); - void addAccessPoint(NMAccessPointAdapter* ap); - void removeAccessPoint(NMAccessPointAdapter* ap); - void updateSignalStrength(); - [[nodiscard]] bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; - -signals: - void signalStrengthChanged(quint8 signal); - -private: - QList mAccessPoints; - QByteArray mSsid; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::signalStrengthChanged); - // clang-format on -}; - -// NMWirelessAdapter wraps the state of a NetworkManager wireless device -// (org.freedesktop.NetworkManager.Device.Wireless), provides signals/slots that connect to a -// frontend NetworkWifiDevice, and creates/destroys NMAccessPointAdapters and NMAccessPointGroups -class NMWirelessAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMWirelessAdapter(const QString& path, QObject* parent = nullptr); - - void registerAccessPoint(const QString& path); - void registerAccessPoints(); - void addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid); - void removeApFromNetwork(NMAccessPointAdapter* ap); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; - [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }; - -public slots: - void scan(); - -signals: - void lastScanChanged(qint64 lastScan); - void activeApChanged(const QDBusObjectPath& path); - - void wifiNetworkAdded(WifiNetwork* network); - void wifiNetworkRemoved(WifiNetwork* network); - -private slots: - void onAccessPointAdded(const QDBusObjectPath& path); - void onAccessPointRemoved(const QDBusObjectPath& path); - void onActiveApChanged(const QDBusObjectPath& path); - -private: - // Lookups: AP <-> AP group <-> frontend wifi network - QHash mApMap; // AP Path -> NMAccessPointAdapter* - QHash mSsidMap; // AP Path -> Ssid - QHash mApGroupMap; // Ssid -> NMAccessPointGroup* - QHash mWifiNetworkMap; // Ssid -> NetworkWifiNetwork* - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, QDBusObjectPath, bActiveAccessPoint, &NMWirelessAdapter::activeApChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); - QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); - QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); - // clang-format on - - DBusNMWirelessProxy* proxy = nullptr; -}; - -} // namespace qs::network From e5e0544ea323f4a2b53164461629ad4af35222e3 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Wed, 23 Jul 2025 13:48:48 -0500 Subject: [PATCH 30/32] feat: backend connections Added adapters to the Settings and Settings.Connections NetworkManager interfaces. Fixed the NetworkBackend constructor to wait for the ready status of each backend before trying the next. --- src/network/CMakeLists.txt | 21 +++ src/network/api.cpp | 23 ++- src/network/api.hpp | 4 +- src/network/nm/accesspoint.cpp | 2 +- src/network/nm/accesspoint.hpp | 2 +- src/network/nm/backend.cpp | 16 ++- src/network/nm/backend.hpp | 5 +- src/network/nm/connection.cpp | 134 ++++++++++++++++++ src/network/nm/connection.hpp | 58 ++++++++ src/network/nm/device.cpp | 2 +- src/network/nm/device.hpp | 4 +- ...top.NetworkManager.Settings.Connection.xml | 16 +++ ...rg.freedesktop.NetworkManager.Settings.xml | 14 ++ 13 files changed, 277 insertions(+), 24 deletions(-) create mode 100644 src/network/nm/connection.cpp create mode 100644 src/network/nm/connection.hpp create mode 100644 src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml create mode 100644 src/network/nm/org.freedesktop.NetworkManager.Settings.xml diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index f82aee9d..3c5527a3 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -39,12 +39,33 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES nm/dbus_nm_accesspoint ) +set_source_files_properties(nm/org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES + CLASSNAME DBusNMConnectionProxy + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + nm/org.freedesktop.NetworkManager.Settings.Connection.xml + nm/dbus_nm_connection +) + +set_source_files_properties(nm/org.freedesktop.NetworkManager.Settings.xml PROPERTIES + CLASSNAME DBusNMSettingsProxy + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + nm/org.freedesktop.NetworkManager.Settings.xml + nm/dbus_nm_settings +) + qt_add_library(quickshell-network STATIC api.cpp nm/backend.cpp nm/device.cpp nm/accesspoint.cpp nm/wireless.cpp + nm/connection.cpp ${NM_DBUS_INTERFACES} ) diff --git a/src/network/api.cpp b/src/network/api.cpp index a255d599..77693a86 100644 --- a/src/network/api.cpp +++ b/src/network/api.cpp @@ -123,18 +123,17 @@ Network::Network(QObject* parent): QObject(parent) { // Try each backend // NetworkManager - auto* nm = new NetworkManager(); - if (nm->isAvailable()) { - QObject::connect(nm, &NetworkManager::deviceAdded, this, &Network::addDevice); - QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); - this->backend = nm; - return; - } else { - delete nm; - } - - // None found - qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; + auto* nm = new NetworkManager(this); + connect(nm, &NetworkBackend::ready, this, [this, nm](bool success) { + if (success) { + QObject::connect(nm, &NetworkManager::deviceAdded, this, &Network::addDevice); + QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); + this->backend = nm; + } else { + delete nm; + qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; + } + }); } void Network::addDevice(NetworkDevice* device) { this->mDevices.insertObject(device); } diff --git a/src/network/api.hpp b/src/network/api.hpp index 6f047747..b517bbdc 100644 --- a/src/network/api.hpp +++ b/src/network/api.hpp @@ -183,8 +183,8 @@ public slots: class NetworkBackend: public QObject { Q_OBJECT; -public: - [[nodiscard]] virtual bool isAvailable() const = 0; +signals: + void ready(bool success); protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp index 1eb27e76..977fc38c 100644 --- a/src/network/nm/accesspoint.cpp +++ b/src/network/nm/accesspoint.cpp @@ -9,7 +9,7 @@ #include #include "../../dbus/properties.hpp" -#include "dbus_nm_accesspoint.h" +#include "nm/dbus_nm_accesspoint.h" using namespace qs::dbus; diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp index 785148c8..bbf3b11e 100644 --- a/src/network/nm/accesspoint.hpp +++ b/src/network/nm/accesspoint.hpp @@ -11,7 +11,7 @@ #include #include "../../dbus/properties.hpp" -#include "dbus_nm_accesspoint.h" +#include "nm/dbus_nm_accesspoint.h" namespace qs::network { diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index 74da3274..25e834db 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -14,8 +14,8 @@ #include "../../dbus/properties.hpp" #include "../api.hpp" #include "device.hpp" +#include "nm/dbus_nm_backend.h" #include "wireless.hpp" -#include "dbus_nm_backend.h" namespace qs::network { @@ -25,6 +25,7 @@ Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWar const QString NM_SERVICE = "org.freedesktop.NetworkManager"; const QString NM_PATH = "/org/freedesktop/NetworkManager"; +const QString NM_SETTINGS_PATH = "/org/freedesktop/NetworkManager/Settings"; NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { qCDebug(logNetworkManager) << "Starting NetworkManager Network Backend"; @@ -33,6 +34,7 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { if (!bus.isConnected()) { qCWarning(logNetworkManager ) << "Could not connect to DBus. NetworkManager backend will not work."; + emit ready(false); return; } @@ -49,6 +51,7 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { } else { qCWarning(logNetworkManager) << "Could not start NetworkManager. This network backend will not work."; + emit ready(false); } }); } else { @@ -57,6 +60,15 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { } void NetworkManager::init() { + emit ready(true); + + this->settings = new NMSettingsAdapter(NM_SETTINGS_PATH, this); + if (!this->settings->isValid()) { + qCWarning(logNetworkManager) << "Failed to connect to NetworkManager settings service."; + delete this->settings; + return; + } + QObject::connect( this->proxy, &DBusNetworkManagerProxy::DeviceAdded, @@ -232,6 +244,4 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { } } -bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); } - } // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index 23139e7f..be7f6ccb 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -12,8 +12,9 @@ #include "../../dbus/properties.hpp" #include "../api.hpp" -#include "dbus_nm_backend.h" +#include "connection.hpp" #include "device.hpp" +#include "nm/dbus_nm_backend.h" namespace qs::network { @@ -26,7 +27,6 @@ class NetworkManager: public NetworkBackend { public: explicit NetworkManager(QObject* parent = nullptr); - [[nodiscard]] bool isAvailable() const override; private slots: void onDeviceAdded(const QDBusObjectPath& path); @@ -42,6 +42,7 @@ private slots: NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); QHash mDeviceHash; + NMSettingsAdapter* settings = nullptr; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); DBusNetworkManagerProxy* proxy = nullptr; diff --git a/src/network/nm/connection.cpp b/src/network/nm/connection.cpp new file mode 100644 index 00000000..27b85857 --- /dev/null +++ b/src/network/nm/connection.cpp @@ -0,0 +1,134 @@ +#include "connection.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "nm/dbus_nm_connection.h" + +using namespace qs::dbus; + +namespace qs::network { + +namespace { +Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); +} + +NMConnectionAdapter::NMConnectionAdapter(const QString& path, QObject* parent): QObject(parent) { + this->proxy = new DBusNMConnectionProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; + return; + } + + this->connectionProperties.setInterface(this->proxy); + this->connectionProperties.updateAllViaGetAll(); +} + +bool NMConnectionAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMConnectionAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMConnectionAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +NMSettingsAdapter::NMSettingsAdapter(const QString& path, QObject* parent): QObject(parent) { + this->proxy = new DBusNMSettingsProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->proxy->isValid()) { + qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; + return; + } + + QObject::connect( + this->proxy, + &DBusNMSettingsProxy::NewConnection, + this, + &NMSettingsAdapter::onNewConnection + ); + + QObject::connect( + this->proxy, + &DBusNMSettingsProxy::ConnectionRemoved, + this, + &NMSettingsAdapter::onConnectionRemoved + ); + + this->settingsProperties.setInterface(this->proxy); + this->settingsProperties.updateAllViaGetAll(); + + this->registerConnections(); +} + +void NMSettingsAdapter::onNewConnection(const QDBusObjectPath& path) { + this->registerConnection(path.path()); +} + +void NMSettingsAdapter::onConnectionRemoved(const QDBusObjectPath& path) { + auto* connection = this->mConnectionMap.take(path.path()); + + if (!connection) { + qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path.path() + << "which is not registered."; + return; + } + + delete connection; +} + +void NMSettingsAdapter::registerConnections() { + auto pending = this->proxy->ListConnections(); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply> reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) << "Failed to get connections: " << reply.error().message(); + } else { + for (const QDBusObjectPath& devicePath: reply.value()) { + this->registerConnection(devicePath.path()); + } + } + + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NMSettingsAdapter::registerConnection(const QString& path) { + auto* connection = new NMConnectionAdapter(path, this); + + if (!connection->isValid()) { + qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; + delete connection; + return; + } + + this->mConnectionMap.insert(path, connection); + qCDebug(logNetworkManager) << "Registered connection" << connection; +} + +bool NMSettingsAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } +QString NMSettingsAdapter::address() const { + return this->proxy ? this->proxy->service() : QString(); +} +QString NMSettingsAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } + +} // namespace qs::network diff --git a/src/network/nm/connection.hpp b/src/network/nm/connection.hpp new file mode 100644 index 00000000..50ed8c47 --- /dev/null +++ b/src/network/nm/connection.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../dbus/properties.hpp" +#include "nm/dbus_nm_connection.h" +#include "nm/dbus_nm_settings.h" + +namespace qs::network { + +class NMConnectionAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMConnectionAdapter(const QString& path, QObject* parent = nullptr); + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + +private: + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionAdapter, connectionProperties); + DBusNMConnectionProxy* proxy = nullptr; +}; + +class NMSettingsAdapter: public QObject { + Q_OBJECT; + +public: + explicit NMSettingsAdapter(const QString& path, QObject* parent = nullptr); + + void registerConnections(); + void registerConnection(const QString& path); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] QString path() const; + [[nodiscard]] QString address() const; + +private slots: + void onNewConnection(const QDBusObjectPath& path); + void onConnectionRemoved(const QDBusObjectPath& path); + +private: + QHash mConnectionMap; + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettingsAdapter, settingsProperties); + + DBusNMSettingsProxy* proxy = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index 590e435a..4852e478 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -9,7 +9,7 @@ #include #include "../../dbus/properties.hpp" -#include "dbus_nm_device.h" +#include "nm/dbus_nm_device.h" using namespace qs::dbus; diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp index 0df47166..86c0e1d1 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -11,8 +11,8 @@ #include #include "../../dbus/properties.hpp" -#include "api.hpp" -#include "dbus_nm_device.h" +#include "../api.hpp" +#include "nm/dbus_nm_device.h" namespace qs::network { diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml new file mode 100644 index 00000000..28b505ef --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.xml new file mode 100644 index 00000000..9a8ce360 --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.Settings.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From be80d20e8a43806e51bcae33eedfd3a7e2128004 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Thu, 24 Jul 2025 15:05:54 -0500 Subject: [PATCH 31/32] feat: ap flags, device connections --- src/network/CMakeLists.txt | 12 +-- src/network/{api.cpp => frontend.cpp} | 22 ++--- src/network/{api.hpp => frontend.hpp} | 4 +- src/network/nm/accesspoint.cpp | 24 +++-- src/network/nm/accesspoint.hpp | 80 +++++++++++++++-- src/network/nm/backend.cpp | 29 +----- src/network/nm/backend.hpp | 4 +- src/network/nm/connection.cpp | 89 ------------------- src/network/nm/connection.hpp | 26 ------ src/network/nm/device.cpp | 36 ++++++++ src/network/nm/device.hpp | 15 +++- ...freedesktop.NetworkManager.AccessPoint.xml | 4 + .../org.freedesktop.NetworkManager.Device.xml | 1 + ...rg.freedesktop.NetworkManager.Settings.xml | 14 --- src/network/nm/wireless.cpp | 6 +- src/network/nm/wireless.hpp | 6 +- 16 files changed, 174 insertions(+), 198 deletions(-) rename src/network/{api.cpp => frontend.cpp} (89%) rename src/network/{api.hpp => frontend.hpp} (99%) delete mode 100644 src/network/nm/org.freedesktop.NetworkManager.Settings.xml diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 3c5527a3..80478164 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -49,18 +49,8 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES nm/dbus_nm_connection ) -set_source_files_properties(nm/org.freedesktop.NetworkManager.Settings.xml PROPERTIES - CLASSNAME DBusNMSettingsProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - nm/org.freedesktop.NetworkManager.Settings.xml - nm/dbus_nm_settings -) - qt_add_library(quickshell-network STATIC - api.cpp + frontend.cpp nm/backend.cpp nm/device.cpp nm/accesspoint.cpp diff --git a/src/network/api.cpp b/src/network/frontend.cpp similarity index 89% rename from src/network/api.cpp rename to src/network/frontend.cpp index 77693a86..1fad7336 100644 --- a/src/network/api.cpp +++ b/src/network/frontend.cpp @@ -1,4 +1,4 @@ -#include "api.hpp" +#include "frontend.hpp" #include #include @@ -124,16 +124,16 @@ Network::Network(QObject* parent): QObject(parent) { // NetworkManager auto* nm = new NetworkManager(this); - connect(nm, &NetworkBackend::ready, this, [this, nm](bool success) { - if (success) { - QObject::connect(nm, &NetworkManager::deviceAdded, this, &Network::addDevice); - QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); - this->backend = nm; - } else { - delete nm; - qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; - } - }); + if (nm->isAvailable()) { + QObject::connect(nm, &NetworkManager::deviceAdded, this, &Network::addDevice); + QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Network::removeDevice); + this->backend = nm; + return; + } else { + delete nm; + } + + qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; } void Network::addDevice(NetworkDevice* device) { this->mDevices.insertObject(device); } diff --git a/src/network/api.hpp b/src/network/frontend.hpp similarity index 99% rename from src/network/api.hpp rename to src/network/frontend.hpp index b517bbdc..6f047747 100644 --- a/src/network/api.hpp +++ b/src/network/frontend.hpp @@ -183,8 +183,8 @@ public slots: class NetworkBackend: public QObject { Q_OBJECT; -signals: - void ready(bool success); +public: + [[nodiscard]] virtual bool isAvailable() const = 0; protected: explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp index 977fc38c..52b6d395 100644 --- a/src/network/nm/accesspoint.cpp +++ b/src/network/nm/accesspoint.cpp @@ -42,11 +42,11 @@ QString NMAccessPointAdapter::address() const { } QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NMAccessPointGroup::NMAccessPointGroup(QByteArray ssid, QObject* parent) +NMWifiNetwork::NMWifiNetwork(QByteArray ssid, QObject* parent) : QObject(parent) , mSsid(std::move(ssid)) {} -void NMAccessPointGroup::updateSignalStrength() { +void NMWifiNetwork::updateSignalStrength() { quint8 max = 0; for (auto* ap: mAccessPoints) { max = qMax(max, ap->getSignal()); @@ -56,7 +56,7 @@ void NMAccessPointGroup::updateSignalStrength() { } } -void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { +void NMWifiNetwork::addAccessPoint(NMAccessPointAdapter* ap) { if (this->mAccessPoints.contains(ap)) { qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in AP group"; return; @@ -67,12 +67,12 @@ void NMAccessPointGroup::addAccessPoint(NMAccessPointAdapter* ap) { ap, &NMAccessPointAdapter::signalStrengthChanged, this, - &NMAccessPointGroup::updateSignalStrength + &NMWifiNetwork::updateSignalStrength ); this->updateSignalStrength(); } -void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { +void NMWifiNetwork::removeAccessPoint(NMAccessPointAdapter* ap) { if (mAccessPoints.removeOne(ap)) { QObject::disconnect(ap, nullptr, this, nullptr); this->updateSignalStrength(); @@ -80,3 +80,17 @@ void NMAccessPointGroup::removeAccessPoint(NMAccessPointAdapter* ap) { } } // namespace qs::network + +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp index bbf3b11e..f185d5da 100644 --- a/src/network/nm/accesspoint.hpp +++ b/src/network/nm/accesspoint.hpp @@ -15,6 +15,66 @@ namespace qs::network { +class NM80211ApFlags: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + None = 0, + Privacy = 1, + Wps = 2, + WpsPbc = 4, + WpsPin = 8, + }; + Q_ENUM(Enum); +}; + +class NM80211ApSecurityFlags: public QObject { + Q_OBJECT; + +public: + enum Enum : quint16 { + None = 0, + PairWep40 = 1, + PairWep104 = 2, + PairTkip = 4, + PairCcmp = 8, + GroupWep40 = 16, + GroupWep104 = 32, + GroupTkip = 64, + GroupCcmp = 128, + KeyMgmtPsk = 256, + KeyMgmt8021x = 512, + KeyMgmtSae = 1024, + KeyMgmtOwe = 2048, + KeyMgmtOweTm = 4096, + KeyMgmtEapSuiteB192 = 8192, + }; + Q_ENUM(Enum); +}; + +} // namespace qs::network + +namespace qs::dbus { + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NM80211ApFlags::Enum; + static DBusResult fromWire(Wire wire); +}; + +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NM80211ApSecurityFlags::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus + +namespace qs::network { + // NMAccessPointAdapter wraps the state of a NetworkManager access point // (org.freedesktop.NetworkManager.AccessPoint) class NMAccessPointAdapter: public QObject { @@ -31,28 +91,36 @@ class NMAccessPointAdapter: public QObject { signals: void ssidChanged(const QByteArray& ssid); void signalStrengthChanged(quint8 signal); + void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags); + void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags); + void flagsChanged(NM80211ApFlags::Enum flags); private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, QByteArray, bSsid, &NMAccessPointAdapter::ssidChanged); Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, quint8, bSignalStrength, &NMAccessPointAdapter::signalStrengthChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApFlags::Enum, bFlags, &NMAccessPointAdapter::flagsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPointAdapter::wpaFlagsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPointAdapter::rsnFlagsChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pFlags, bFlags, accessPointProperties, "Flags"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags"); // clang-format on DBusNMAccessPointProxy* proxy = nullptr; }; -// NMAccessPointGroup bundles the state of access points with the same SSID -// into a single object and supplies signals/slots to connect AP signal strength -// to NetworkWifiNetwork signal strength -class NMAccessPointGroup: public QObject { +// NMWifiNetwork represents a wireless network, which aggregates all access points with +// the same SSID. It also provides signals and slots for a frontend WifiNetwork. +class NMWifiNetwork: public QObject { Q_OBJECT; public: - explicit NMAccessPointGroup(QByteArray ssid, QObject* parent = nullptr); + explicit NMWifiNetwork(QByteArray ssid, QObject* parent = nullptr); void addAccessPoint(NMAccessPointAdapter* ap); void removeAccessPoint(NMAccessPointAdapter* ap); void updateSignalStrength(); @@ -66,7 +134,7 @@ class NMAccessPointGroup: public QObject { QByteArray mSsid; // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointGroup, quint8, bMaxSignal, &NMAccessPointGroup::signalStrengthChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWifiNetwork, quint8, bMaxSignal, &NMWifiNetwork::signalStrengthChanged); // clang-format on }; diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index 25e834db..784c009c 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -10,9 +10,8 @@ #include #include -#include "../../dbus/bus.hpp" #include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../frontend.hpp" #include "device.hpp" #include "nm/dbus_nm_backend.h" #include "wireless.hpp" @@ -25,7 +24,6 @@ Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWar const QString NM_SERVICE = "org.freedesktop.NetworkManager"; const QString NM_PATH = "/org/freedesktop/NetworkManager"; -const QString NM_SETTINGS_PATH = "/org/freedesktop/NetworkManager/Settings"; NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { qCDebug(logNetworkManager) << "Starting NetworkManager Network Backend"; @@ -34,7 +32,6 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { if (!bus.isConnected()) { qCWarning(logNetworkManager ) << "Could not connect to DBus. NetworkManager backend will not work."; - emit ready(false); return; } @@ -42,33 +39,13 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { if (!this->proxy->isValid()) { qCDebug(logNetworkManager - ) << "NetworkManager service is not currently running, attempting to start it."; - - dbus::tryLaunchService(this, bus, NM_SERVICE, [this](bool success) { - if (success) { - qCDebug(logNetworkManager) << "Successfully launched NetworkManager backend."; - this->init(); - } else { - qCWarning(logNetworkManager) - << "Could not start NetworkManager. This network backend will not work."; - emit ready(false); - } - }); + ) << "NetworkManager service is not currently running. This network backend will not work"; } else { this->init(); } } void NetworkManager::init() { - emit ready(true); - - this->settings = new NMSettingsAdapter(NM_SETTINGS_PATH, this); - if (!this->settings->isValid()) { - qCWarning(logNetworkManager) << "Failed to connect to NetworkManager settings service."; - delete this->settings; - return; - } - QObject::connect( this->proxy, &DBusNetworkManagerProxy::DeviceAdded, @@ -244,4 +221,6 @@ void NetworkManager::onDeviceRemoved(const QDBusObjectPath& path) { } } +bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); }; + } // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index be7f6ccb..38080758 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -11,7 +11,7 @@ #include #include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../frontend.hpp" #include "connection.hpp" #include "device.hpp" #include "nm/dbus_nm_backend.h" @@ -27,6 +27,7 @@ class NetworkManager: public NetworkBackend { public: explicit NetworkManager(QObject* parent = nullptr); + [[nodiscard]] bool isAvailable() const override; private slots: void onDeviceAdded(const QDBusObjectPath& path); @@ -42,7 +43,6 @@ private slots: NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); QHash mDeviceHash; - NMSettingsAdapter* settings = nullptr; QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); DBusNetworkManagerProxy* proxy = nullptr; diff --git a/src/network/nm/connection.cpp b/src/network/nm/connection.cpp index 27b85857..c248ce95 100644 --- a/src/network/nm/connection.cpp +++ b/src/network/nm/connection.cpp @@ -42,93 +42,4 @@ QString NMConnectionAdapter::address() const { } QString NMConnectionAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NMSettingsAdapter::NMSettingsAdapter(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMSettingsProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; - return; - } - - QObject::connect( - this->proxy, - &DBusNMSettingsProxy::NewConnection, - this, - &NMSettingsAdapter::onNewConnection - ); - - QObject::connect( - this->proxy, - &DBusNMSettingsProxy::ConnectionRemoved, - this, - &NMSettingsAdapter::onConnectionRemoved - ); - - this->settingsProperties.setInterface(this->proxy); - this->settingsProperties.updateAllViaGetAll(); - - this->registerConnections(); -} - -void NMSettingsAdapter::onNewConnection(const QDBusObjectPath& path) { - this->registerConnection(path.path()); -} - -void NMSettingsAdapter::onConnectionRemoved(const QDBusObjectPath& path) { - auto* connection = this->mConnectionMap.take(path.path()); - - if (!connection) { - qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path.path() - << "which is not registered."; - return; - } - - delete connection; -} - -void NMSettingsAdapter::registerConnections() { - auto pending = this->proxy->ListConnections(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to get connections: " << reply.error().message(); - } else { - for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerConnection(devicePath.path()); - } - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NMSettingsAdapter::registerConnection(const QString& path) { - auto* connection = new NMConnectionAdapter(path, this); - - if (!connection->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete connection; - return; - } - - this->mConnectionMap.insert(path, connection); - qCDebug(logNetworkManager) << "Registered connection" << connection; -} - -bool NMSettingsAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMSettingsAdapter::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMSettingsAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } - } // namespace qs::network diff --git a/src/network/nm/connection.hpp b/src/network/nm/connection.hpp index 50ed8c47..4a75d06a 100644 --- a/src/network/nm/connection.hpp +++ b/src/network/nm/connection.hpp @@ -12,7 +12,6 @@ #include "../../dbus/properties.hpp" #include "nm/dbus_nm_connection.h" -#include "nm/dbus_nm_settings.h" namespace qs::network { @@ -30,29 +29,4 @@ class NMConnectionAdapter: public QObject { DBusNMConnectionProxy* proxy = nullptr; }; -class NMSettingsAdapter: public QObject { - Q_OBJECT; - -public: - explicit NMSettingsAdapter(const QString& path, QObject* parent = nullptr); - - void registerConnections(); - void registerConnection(const QString& path); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - -private slots: - void onNewConnection(const QDBusObjectPath& path); - void onConnectionRemoved(const QDBusObjectPath& path); - -private: - QHash mConnectionMap; - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettingsAdapter, settingsProperties); - - DBusNMSettingsProxy* proxy = nullptr; -}; - } // namespace qs::network diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index 4852e478..f446d400 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -32,10 +32,46 @@ NMDeviceAdapter::NMDeviceAdapter(const QString& path, QObject* parent): QObject( return; } + QObject::connect( + this, + &NMDeviceAdapter::availableConnectionsChanged, + this, + &NMDeviceAdapter::onAvailableConnectionsChanged + ); + this->deviceProperties.setInterface(this->proxy); this->deviceProperties.updateAllViaGetAll(); } +void NMDeviceAdapter::onAvailableConnectionsChanged(const QList& paths) { + QSet newConnectionPaths; + for (const QDBusObjectPath& path: paths) { + newConnectionPaths.insert(path.path()); + } + + QSet addedConnections = newConnectionPaths - this->mConnectionPaths; + QSet removedConnections = this->mConnectionPaths - newConnectionPaths; + for (const QString& path: addedConnections) { + auto* connection = new NMConnectionAdapter(path, this); + if (!connection->isValid()) { + qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; + delete connection; + } else { + this->mConnectionMap.insert(path, connection); + qCDebug(logNetworkManager) << "Registered connection" << path; + } + } + for (const QString& path: removedConnections) { + auto* connection = this->mConnectionMap.take(path); + if (!connection) { + qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path + << "which is not registered."; + } else { + delete connection; + } + }; +} + void NMDeviceAdapter::disconnect() { this->proxy->Disconnect(); } bool NMDeviceAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMDeviceAdapter::address() const { diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp index 86c0e1d1..147bbf30 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -11,7 +11,8 @@ #include #include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../frontend.hpp" +#include "connection.hpp" #include "nm/dbus_nm_device.h" namespace qs::network { @@ -130,19 +131,31 @@ public slots: void hwAddressChanged(const QString& hwAddress); void typeChanged(NMDeviceType::Enum type); void stateChanged(NMDeviceState::Enum state); + void connectionAdded(const QString& path); + void connectionRemoved(const QString& path); + void availableConnectionsChanged(QList paths); + +private slots: + void onAvailableConnectionsChanged(const QList& paths); private: + // Connection lookups + QSet mConnectionPaths; + QHash mConnectionMap; + // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bInterface, &NMDeviceAdapter::interfaceChanged); Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QString, bHwAddress, &NMDeviceAdapter::hwAddressChanged); Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceState::Enum, bState, &NMDeviceAdapter::stateChanged); Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, NMDeviceType::Enum, bType, &NMDeviceAdapter::typeChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDeviceAdapter, QList, bAvailableConnections, &NMDeviceAdapter::availableConnectionsChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pName, bInterface, deviceProperties, "Interface"); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAddress, bHwAddress, deviceProperties, "HwAddress"); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pType, bType, deviceProperties, "DeviceType"); QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pState, bState, deviceProperties, "State"); + QS_DBUS_PROPERTY_BINDING(NMDeviceAdapter, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections"); // clang-format on DBusNMDeviceProxy* proxy = nullptr; diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml index 0f5dcf2f..c3f829c3 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml @@ -2,5 +2,9 @@ + + + + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml index f230778f..ba7e68da 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.Device.xml @@ -3,6 +3,7 @@ + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.xml deleted file mode 100644 index 9a8ce360..00000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Settings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp index 3eb27746..46de20ef 100644 --- a/src/network/nm/wireless.cpp +++ b/src/network/nm/wireless.cpp @@ -9,7 +9,7 @@ #include #include "../../dbus/properties.hpp" -#include "dbus_nm_wireless.h" +#include "nm/dbus_nm_wireless.h" using namespace qs::dbus; @@ -155,13 +155,13 @@ void NMWirelessAdapter::addApToNetwork(NMAccessPointAdapter* ap, const QByteArra auto* group = mApGroupMap.value(ssid); if (!group) { auto* network = new WifiNetwork(this); - group = new NMAccessPointGroup(ssid, this); + group = new NMWifiNetwork(ssid, this); network->setSsid(QString::fromUtf8(ssid)); // NMAccessPointGroup signal -> NetworkWifiNetwork slot QObject::connect( group, - &NMAccessPointGroup::signalStrengthChanged, + &NMWifiNetwork::signalStrengthChanged, network, &WifiNetwork::setSignalStrength ); diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp index 5779862f..9101537e 100644 --- a/src/network/nm/wireless.hpp +++ b/src/network/nm/wireless.hpp @@ -11,9 +11,9 @@ #include #include "../../dbus/properties.hpp" -#include "../api.hpp" +#include "../frontend.hpp" #include "accesspoint.hpp" -#include "dbus_nm_wireless.h" +#include "nm/dbus_nm_wireless.h" namespace qs::network { @@ -56,7 +56,7 @@ private slots: // Lookups: AP <-> AP group <-> frontend wifi network QHash mApMap; // AP Path -> NMAccessPointAdapter* QHash mSsidMap; // AP Path -> Ssid - QHash mApGroupMap; // Ssid -> NMAccessPointGroup* + QHash mApGroupMap; // Ssid -> NMAccessPointGroup* QHash mWifiNetworkMap; // Ssid -> NetworkWifiNetwork* // clang-format off From 840e6ec78e7d37b2fa66cb46264f865e25c68ef2 Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Mon, 28 Jul 2025 13:14:12 -0500 Subject: [PATCH 32/32] feat: merge backend connections + networks -> WifiNetwork. Adds NMWirelessManager which merges identical NMWirelessNetworks (groups of APs with the same SSID) and NMConnectionAdapters (saved connection settings to wifi networks), and creates WifiNetworks on the frontend. The manager takes a similar approach to plasma-nm and nm-applet. Adds utilities to determine a connections security type and a networks best choice for security, in sync with nm_utils_security_valid from nmcli. --- src/network/CMakeLists.txt | 6 +- src/network/frontend.cpp | 22 +- src/network/frontend.hpp | 30 +- src/network/nm/accesspoint.cpp | 79 ++-- src/network/nm/accesspoint.hpp | 85 +--- src/network/nm/backend.cpp | 138 +++---- src/network/nm/backend.hpp | 8 +- src/network/nm/connection.cpp | 34 +- src/network/nm/connection.hpp | 12 + src/network/nm/dbus_types.hpp | 9 + src/network/nm/device.cpp | 58 ++- src/network/nm/device.hpp | 86 +--- src/network/nm/enums.hpp | 202 ++++++++++ ...freedesktop.NetworkManager.AccessPoint.xml | 1 + ...top.NetworkManager.Settings.Connection.xml | 4 +- src/network/nm/utils.cpp | 342 ++++++++++++++++ src/network/nm/utils.hpp | 46 +++ src/network/nm/wireless.cpp | 367 ++++++++++++++---- src/network/nm/wireless.hpp | 95 ++++- src/network/test/network.qml | 24 +- 20 files changed, 1215 insertions(+), 433 deletions(-) create mode 100644 src/network/nm/dbus_types.hpp create mode 100644 src/network/nm/enums.hpp create mode 100644 src/network/nm/utils.cpp create mode 100644 src/network/nm/utils.hpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 80478164..8d9899f8 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -41,7 +41,7 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES set_source_files_properties(nm/org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES CLASSNAME DBusNMConnectionProxy - NO_NAMESPACE TRUE + INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/nm/dbus_types.hpp ) qt_add_dbus_interface(NM_DBUS_INTERFACES @@ -53,9 +53,11 @@ qt_add_library(quickshell-network STATIC frontend.cpp nm/backend.cpp nm/device.cpp + nm/connection.cpp nm/accesspoint.cpp nm/wireless.cpp - nm/connection.cpp + nm/utils.cpp + nm/enums.hpp ${NM_DBUS_INTERFACES} ) diff --git a/src/network/frontend.cpp b/src/network/frontend.cpp index 1fad7336..5968eb76 100644 --- a/src/network/frontend.cpp +++ b/src/network/frontend.cpp @@ -92,9 +92,13 @@ void NetworkWifiDevice::scan() { this->requestScan(); } -void NetworkWifiDevice::addNetwork(WifiNetwork* network) { mNetworks.insertObject(network); } +void NetworkWifiDevice::wifiNetworkAdded(WifiNetwork* network) { + this->mNetworks.insertObject(network); +} -void NetworkWifiDevice::removeNetwork(WifiNetwork* network) { mNetworks.removeObject(network); } +void NetworkWifiDevice::wifiNetworkRemoved(WifiNetwork* network) { + this->mNetworks.removeObject(network); +} WifiNetwork::WifiNetwork(QObject* parent): QObject(parent) {}; @@ -119,6 +123,20 @@ void WifiNetwork::setConnected(bool connected) { } } +void WifiNetwork::setNmSecurity(NMWirelessSecurityType::Enum security) { + if (this->bNmSecurity != security) { + this->bNmSecurity = security; + emit this->nmSecurityChanged(); + } +} + +void WifiNetwork::setKnown(bool known) { + if (this->bKnown != known) { + this->bKnown = known; + emit this->knownChanged(); + } +} + Network::Network(QObject* parent): QObject(parent) { // Try each backend diff --git a/src/network/frontend.hpp b/src/network/frontend.hpp index 6f047747..11072200 100644 --- a/src/network/frontend.hpp +++ b/src/network/frontend.hpp @@ -11,32 +11,42 @@ #include #include "../core/model.hpp" +#include "nm/enums.hpp" namespace qs::network { -///! A wifi network +///! A wifi network represents an available connection or access point on a wireless device. class WifiNetwork: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("Wifi networks can only be acquired through Network"); + QML_UNCREATABLE("Wifi items can only be acquired through Network"); // clang-format off - /// The service set identifier of the wifi network + /// The SSID (service set identifier) of the wifi network Q_PROPERTY(QString ssid READ default NOTIFY ssidChanged BINDABLE bindableSsid); // The current signal strength of the best access point on the network, in percent. Q_PROPERTY(quint8 signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength); - /// True if the wireless device is curerntly connected to this wifi network. + /// True if the wireless device is currently connected to this wifi network. Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); + /// The security type of the wifi network when the backend is NetworkManager. Otherwise NMWirelessSecurityType::Unknown. + Q_PROPERTY(NMWirelessSecurityType::Enum nmSecurity READ default NOTIFY nmSecurityChanged BINDABLE bindableNmSecurity); + /// True if the wifi network has a known connection profile saved. + Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown); // clang-format on signals: void ssidChanged(); void signalStrengthChanged(); void connectedChanged(); + void nmSecurityChanged(); + void knownChanged(); + void requestConnect(); public slots: void setSsid(const QString& ssid); void setSignalStrength(quint8 signalStrength); void setConnected(bool connected); + void setNmSecurity(NMWirelessSecurityType::Enum security); + void setKnown(bool known); public: explicit WifiNetwork(QObject* parent = nullptr); @@ -44,12 +54,20 @@ public slots: [[nodiscard]] QBindable bindableSsid() const { return &this->bSsid; }; [[nodiscard]] QBindable bindableSignalStrength() const { return &this->bSignalStrength; }; [[nodiscard]] QBindable bindableConnected() const { return &this->bConnected; }; + [[nodiscard]] QBindable bindableNmSecurity() const { + return &this->bNmSecurity; + }; + [[nodiscard]] QBindable bindableKnown() const { return &this->bKnown; }; + + // Q_INVOKABLE void connect(); private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, QString, bSsid, &WifiNetwork::ssidChanged); Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, quint8, bSignalStrength, &WifiNetwork::signalStrengthChanged); Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bConnected, &WifiNetwork::connectedChanged); + Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, NMWirelessSecurityType::Enum, bNmSecurity, &WifiNetwork::nmSecurityChanged); + Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bKnown, &WifiNetwork::knownChanged); // clang-format on }; @@ -160,8 +178,8 @@ class NetworkWifiDevice: public NetworkDevice { public slots: void scanComplete(); - void addNetwork(WifiNetwork* network); - void removeNetwork(WifiNetwork* network); + void wifiNetworkAdded(WifiNetwork* network); + void wifiNetworkRemoved(WifiNetwork* network); public: explicit NetworkWifiDevice(QObject* parent = nullptr); diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp index 52b6d395..7467f085 100644 --- a/src/network/nm/accesspoint.cpp +++ b/src/network/nm/accesspoint.cpp @@ -11,9 +11,27 @@ #include "../../dbus/properties.hpp" #include "nm/dbus_nm_accesspoint.h" -using namespace qs::dbus; +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus namespace qs::network { +using namespace qs::dbus; namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); @@ -32,6 +50,14 @@ NMAccessPointAdapter::NMAccessPointAdapter(const QString& path, QObject* parent) return; } + QObject::connect( + &this->accessPointProperties, + &DBusPropertyGroup::getAllFinished, + this, + [this]() { emit this->ready(); }, + Qt::SingleShotConnection + ); + this->accessPointProperties.setInterface(this->proxy); this->accessPointProperties.updateAllViaGetAll(); } @@ -42,55 +68,4 @@ QString NMAccessPointAdapter::address() const { } QString NMAccessPointAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NMWifiNetwork::NMWifiNetwork(QByteArray ssid, QObject* parent) - : QObject(parent) - , mSsid(std::move(ssid)) {} - -void NMWifiNetwork::updateSignalStrength() { - quint8 max = 0; - for (auto* ap: mAccessPoints) { - max = qMax(max, ap->getSignal()); - } - if (this->bMaxSignal != max) { - this->bMaxSignal = max; - } -} - -void NMWifiNetwork::addAccessPoint(NMAccessPointAdapter* ap) { - if (this->mAccessPoints.contains(ap)) { - qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in AP group"; - return; - } - - this->mAccessPoints.append(ap); - QObject::connect( - ap, - &NMAccessPointAdapter::signalStrengthChanged, - this, - &NMWifiNetwork::updateSignalStrength - ); - this->updateSignalStrength(); -} - -void NMWifiNetwork::removeAccessPoint(NMAccessPointAdapter* ap) { - if (mAccessPoints.removeOne(ap)) { - QObject::disconnect(ap, nullptr, this, nullptr); - this->updateSignalStrength(); - } -} - } // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp index f185d5da..15311cc0 100644 --- a/src/network/nm/accesspoint.hpp +++ b/src/network/nm/accesspoint.hpp @@ -11,50 +11,9 @@ #include #include "../../dbus/properties.hpp" +#include "enums.hpp" #include "nm/dbus_nm_accesspoint.h" -namespace qs::network { - -class NM80211ApFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - None = 0, - Privacy = 1, - Wps = 2, - WpsPbc = 4, - WpsPin = 8, - }; - Q_ENUM(Enum); -}; - -class NM80211ApSecurityFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint16 { - None = 0, - PairWep40 = 1, - PairWep104 = 2, - PairTkip = 4, - PairCcmp = 8, - GroupWep40 = 16, - GroupWep104 = 32, - GroupTkip = 64, - GroupCcmp = 128, - KeyMgmtPsk = 256, - KeyMgmt8021x = 512, - KeyMgmtSae = 1024, - KeyMgmtOwe = 2048, - KeyMgmtOweTm = 4096, - KeyMgmtEapSuiteB192 = 8192, - }; - Q_ENUM(Enum); -}; - -} // namespace qs::network - namespace qs::dbus { template <> @@ -71,6 +30,13 @@ struct DBusDataTransform { static DBusResult fromWire(Wire wire); }; +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NM80211Mode::Enum; + static DBusResult fromWire(Wire wire); +}; + } // namespace qs::dbus namespace qs::network { @@ -86,7 +52,12 @@ class NMAccessPointAdapter: public QObject { [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; - [[nodiscard]] quint8 getSignal() const { return this->bSignalStrength; }; + [[nodiscard]] QByteArray ssid() const { return this->bSsid; }; + [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }; + [[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; }; + [[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; }; + [[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; }; + [[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; }; signals: void ssidChanged(const QByteArray& ssid); @@ -94,6 +65,8 @@ class NMAccessPointAdapter: public QObject { void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags); void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags); void flagsChanged(NM80211ApFlags::Enum flags); + void modeChanged(NM80211Mode::Enum mode); + void ready(); private: // clang-format off @@ -102,6 +75,7 @@ class NMAccessPointAdapter: public QObject { Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApFlags::Enum, bFlags, &NMAccessPointAdapter::flagsChanged); Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPointAdapter::wpaFlagsChanged); Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPointAdapter::rsnFlagsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMAccessPointAdapter, NM80211Mode::Enum, bMode, &NMAccessPointAdapter::modeChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pSsid, bSsid, accessPointProperties, "Ssid"); @@ -109,33 +83,10 @@ class NMAccessPointAdapter: public QObject { QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pFlags, bFlags, accessPointProperties, "Flags"); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags"); QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags"); + QS_DBUS_PROPERTY_BINDING(NMAccessPointAdapter, pMode, bMode, accessPointProperties, "Mode"); // clang-format on DBusNMAccessPointProxy* proxy = nullptr; }; -// NMWifiNetwork represents a wireless network, which aggregates all access points with -// the same SSID. It also provides signals and slots for a frontend WifiNetwork. -class NMWifiNetwork: public QObject { - Q_OBJECT; - -public: - explicit NMWifiNetwork(QByteArray ssid, QObject* parent = nullptr); - void addAccessPoint(NMAccessPointAdapter* ap); - void removeAccessPoint(NMAccessPointAdapter* ap); - void updateSignalStrength(); - [[nodiscard]] bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; - -signals: - void signalStrengthChanged(quint8 signal); - -private: - QList mAccessPoints; - QByteArray mSsid; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWifiNetwork, quint8, bMaxSignal, &NMWifiNetwork::signalStrengthChanged); - // clang-format on -}; - } // namespace qs::network diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index 784c009c..5af89f47 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -12,7 +12,6 @@ #include "../../dbus/properties.hpp" #include "../frontend.hpp" -#include "device.hpp" #include "nm/dbus_nm_backend.h" #include "wireless.hpp" @@ -46,18 +45,10 @@ NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { } void NetworkManager::init() { - QObject::connect( - this->proxy, - &DBusNetworkManagerProxy::DeviceAdded, - this, - &NetworkManager::onDeviceAdded - ); - QObject::connect( - this->proxy, - &DBusNetworkManagerProxy::DeviceRemoved, - this, - &NetworkManager::onDeviceRemoved - ); + // clang-format off + QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDeviceAdded); + QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDeviceRemoved); + // clang-format on this->dbusProperties.setInterface(this->proxy); this->dbusProperties.updateAllViaGetAll(); @@ -100,55 +91,43 @@ void NetworkManager::queueDeviceRegistration(const QString& path) { return; } - // Wait to receive NMDeviceType before registering device QObject::connect( deviceAdapter, - &NMDeviceAdapter::typeChanged, + &NMDeviceAdapter::ready, this, - [this, deviceAdapter, path](NMDeviceType::Enum type) { - this->registerDevice(deviceAdapter, type, path); - }, + [this, deviceAdapter, path]() { this->registerDevice(deviceAdapter, path); }, Qt::SingleShotConnection ); } -void NetworkManager::registerDevice( - NMDeviceAdapter* deviceAdapter, - NMDeviceType::Enum type, - const QString& path -) { - NetworkDevice* device = createDeviceVariant(type, path); +NetworkDeviceState::Enum NetworkManager::toNetworkDeviceState(NMDeviceState::Enum state) { + switch (state) { + case 0 ... 20: return NetworkDeviceState::Unknown; + case 30: return NetworkDeviceState::Disconnected; + case 40 ... 90: return NetworkDeviceState::Connecting; + case 100: return NetworkDeviceState::Connected; + case 110 ... 120: return NetworkDeviceState::Disconnecting; + } +} + +void NetworkManager::registerDevice(NMDeviceAdapter* deviceAdapter, const QString& path) { + NetworkDevice* device = nullptr; + switch (deviceAdapter->type()) { + case NMDeviceType::Wifi: device = this->bindWirelessDevice(deviceAdapter, path); break; + default: device = new NetworkDevice(this); break; + } deviceAdapter->setParent(device); - // NMDeviceAdapter signal -> NetworkDevice slot - QObject::connect( - deviceAdapter, - &NMDeviceAdapter::hwAddressChanged, - device, - &NetworkDevice::setAddress - ); - QObject::connect( - deviceAdapter, - &NMDeviceAdapter::interfaceChanged, - device, - &NetworkDevice::setName - ); - QObject::connect( - deviceAdapter, - &NMDeviceAdapter::stateChanged, - device, - [device](NMDeviceState::Enum state) { - device->setState(NMDeviceState::toNetworkDeviceState(state)); - } - ); + // clang-format off + QObject::connect(deviceAdapter, &NMDeviceAdapter::hwAddressChanged, device, &NetworkDevice::setAddress); + QObject::connect(deviceAdapter, &NMDeviceAdapter::interfaceChanged, device, &NetworkDevice::setName); + QObject::connect(deviceAdapter, &NMDeviceAdapter::stateChanged, device, [device](NMDeviceState::Enum state) { device->setState(NetworkManager::toNetworkDeviceState(state)); }); + QObject::connect(device, &NetworkDevice::requestDisconnect, deviceAdapter, &NMDeviceAdapter::disconnect); + // clang-format on - // NetworkDevice signal -> NMDeviceAdapter slot - QObject::connect( - device, - &NetworkDevice::requestDisconnect, - deviceAdapter, - &NMDeviceAdapter::disconnect - ); + device->setAddress(deviceAdapter->hwAddress()); + device->setName(deviceAdapter->interface()); + device->setState(NetworkManager::toNetworkDeviceState(deviceAdapter->state())); this->mDeviceHash.insert(path, device); emit deviceAdded(device); @@ -156,48 +135,23 @@ void NetworkManager::registerDevice( qCDebug(logNetworkManager) << "Registered device" << path; } -// Create a device derived from NMDeviceType -NetworkDevice* NetworkManager::createDeviceVariant(NMDeviceType::Enum type, const QString& path) { - switch (type) { - case NMDeviceType::Wifi: return this->bindWirelessDevice(path); - default: return new NetworkDevice(); - } -} - -// Create a WirelessNetworkDevice and connect the NMWirelessAdapter -NetworkWifiDevice* NetworkManager::bindWirelessDevice(const QString& path) { +// Create a NetworkWifiDevice, NMWirelessManager, and connect the adapters +NetworkWifiDevice* +NetworkManager::bindWirelessDevice(NMDeviceAdapter* deviceAdapter, const QString& path) { auto* device = new NetworkWifiDevice(this); auto* wirelessAdapter = new NMWirelessAdapter(path, device); - - // TODO: Check isValid() - throw error - - // NMWirelessAdapter signal -> WirelessNetworkDevice slot - QObject::connect( - wirelessAdapter, - &NMWirelessAdapter::lastScanChanged, - device, - &NetworkWifiDevice::scanComplete - ); - QObject::connect( - wirelessAdapter, - &NMWirelessAdapter::wifiNetworkAdded, - device, - &NetworkWifiDevice::addNetwork - ); - QObject::connect( - wirelessAdapter, - &NMWirelessAdapter::wifiNetworkRemoved, - device, - &NetworkWifiDevice::removeNetwork - ); - - // WirelessNetworkDevice signal -> NMWirelessAdapter slot - QObject::connect( - device, - &NetworkWifiDevice::requestScan, - wirelessAdapter, - &NMWirelessAdapter::scan - ); + auto* manager = new NMWirelessManager(device); + + // clang-format off + QObject::connect(wirelessAdapter, &NMWirelessAdapter::lastScanChanged, device, &NetworkWifiDevice::scanComplete); + QObject::connect(device, &NetworkWifiDevice::requestScan, wirelessAdapter, &NMWirelessAdapter::scan); + QObject::connect(wirelessAdapter, &NMWirelessAdapter::networkAdded, manager, &NMWirelessManager::networkAdded); + QObject::connect(wirelessAdapter, &NMWirelessAdapter::networkRemoved, manager, &NMWirelessManager::networkRemoved); + QObject::connect(deviceAdapter, &NMDeviceAdapter::connectionLoaded, manager, &NMWirelessManager::connectionLoaded); + QObject::connect(deviceAdapter, &NMDeviceAdapter::connectionRemoved, manager, &NMWirelessManager::connectionRemoved); + QObject::connect(manager, &NMWirelessManager::wifiNetworkAdded, device, &NetworkWifiDevice::wifiNetworkAdded); + QObject::connect(manager, &NMWirelessManager::wifiNetworkRemoved, device, &NetworkWifiDevice::wifiNetworkRemoved); + // clang-format on return device; } diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index 38080758..1f396088 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -12,7 +12,6 @@ #include "../../dbus/properties.hpp" #include "../frontend.hpp" -#include "connection.hpp" #include "device.hpp" #include "nm/dbus_nm_backend.h" @@ -35,12 +34,11 @@ private slots: private: void init(); - void registerDevice(NMDeviceAdapter* deviceAdapter, NMDeviceType::Enum type, const QString& path); + void registerDevice(NMDeviceAdapter* deviceAdapter, const QString& path); void registerDevices(); void queueDeviceRegistration(const QString& path); - NetworkDevice* createDeviceVariant(NMDeviceType::Enum type, const QString& path); - NetworkWifiDevice* bindWirelessDevice(const QString& path); - NetworkDevice* bindDevice(NMDeviceAdapter* deviceAdapter); + static NetworkDeviceState::Enum toNetworkDeviceState(NMDeviceState::Enum state); + NetworkWifiDevice* bindWirelessDevice(NMDeviceAdapter* deviceAdapter, const QString& path); QHash mDeviceHash; diff --git a/src/network/nm/connection.cpp b/src/network/nm/connection.cpp index c248ce95..31e2f936 100644 --- a/src/network/nm/connection.cpp +++ b/src/network/nm/connection.cpp @@ -9,17 +9,19 @@ #include #include "../../dbus/properties.hpp" -#include "nm/dbus_nm_connection.h" - -using namespace qs::dbus; namespace qs::network { +using namespace qs::dbus; namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } +// NMConnectionAdapter + NMConnectionAdapter::NMConnectionAdapter(const QString& path, QObject* parent): QObject(parent) { + qDBusRegisterMetaType(); + this->proxy = new DBusNMConnectionProxy( "org.freedesktop.NetworkManager", path, @@ -32,8 +34,34 @@ NMConnectionAdapter::NMConnectionAdapter(const QString& path, QObject* parent): return; } + // clang-format off + QObject::connect(this->proxy, &DBusNMConnectionProxy::Updated, this, &NMConnectionAdapter::updateSettings); + // clang-format on + this->connectionProperties.setInterface(this->proxy); this->connectionProperties.updateAllViaGetAll(); + + this->updateSettings(); +} + +void NMConnectionAdapter::updateSettings() { + auto pending = this->proxy->GetSettings(); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) << "Failed to get settings: " << reply.error().message(); + } else { + this->bSettings = reply.value(); + } + + emit this->ready(); + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); } bool NMConnectionAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } diff --git a/src/network/nm/connection.hpp b/src/network/nm/connection.hpp index 4a75d06a..e6b33453 100644 --- a/src/network/nm/connection.hpp +++ b/src/network/nm/connection.hpp @@ -11,6 +11,8 @@ #include #include "../../dbus/properties.hpp" +#include "dbus_types.hpp" +#include "enums.hpp" #include "nm/dbus_nm_connection.h" namespace qs::network { @@ -20,11 +22,21 @@ class NMConnectionAdapter: public QObject { public: explicit NMConnectionAdapter(const QString& path, QObject* parent = nullptr); + void updateSettings(); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; + [[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; }; + +signals: + void settingsChanged(); + void ready(); private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMConnectionAdapter, ConnectionSettingsMap, bSettings, &NMConnectionAdapter::settingsChanged); + // clang-format on + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionAdapter, connectionProperties); DBusNMConnectionProxy* proxy = nullptr; }; diff --git a/src/network/nm/dbus_types.hpp b/src/network/nm/dbus_types.hpp new file mode 100644 index 00000000..dadbcf38 --- /dev/null +++ b/src/network/nm/dbus_types.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include +#include + +using ConnectionSettingsMap = QMap; +Q_DECLARE_METATYPE(ConnectionSettingsMap); diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index f446d400..fa1a1fb7 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -9,11 +9,23 @@ #include #include "../../dbus/properties.hpp" -#include "nm/dbus_nm_device.h" -using namespace qs::dbus; +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus namespace qs::network { +using namespace qs::dbus; namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); @@ -32,12 +44,10 @@ NMDeviceAdapter::NMDeviceAdapter(const QString& path, QObject* parent): QObject( return; } - QObject::connect( - this, - &NMDeviceAdapter::availableConnectionsChanged, - this, - &NMDeviceAdapter::onAvailableConnectionsChanged - ); + // clang-format off + QObject::connect(this, &NMDeviceAdapter::availableConnectionsChanged, this, &NMDeviceAdapter::onAvailableConnectionsChanged); + QObject::connect(&this->deviceProperties, &DBusPropertyGroup::getAllFinished, this, [this]() { emit this->ready(); }, Qt::SingleShotConnection); + // clang-format on this->deviceProperties.setInterface(this->proxy); this->deviceProperties.updateAllViaGetAll(); @@ -58,6 +68,13 @@ void NMDeviceAdapter::onAvailableConnectionsChanged(const QList delete connection; } else { this->mConnectionMap.insert(path, connection); + QObject::connect( + connection, + &NMConnectionAdapter::ready, + this, + [this, connection]() { emit this->connectionLoaded(connection); }, + Qt::SingleShotConnection + ); qCDebug(logNetworkManager) << "Registered connection" << path; } } @@ -67,6 +84,7 @@ void NMDeviceAdapter::onAvailableConnectionsChanged(const QList qCDebug(logNetworkManager) << "NetworkManager backend sent removal signal for" << path << "which is not registered."; } else { + emit this->connectionRemoved(connection); delete connection; } }; @@ -79,28 +97,4 @@ QString NMDeviceAdapter::address() const { } QString NMDeviceAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } -NetworkDeviceState::Enum NMDeviceState::toNetworkDeviceState(NMDeviceState::Enum state) { - switch (state) { - case 0 ... 20: return NetworkDeviceState::Unknown; - case 30: return NetworkDeviceState::Disconnected; - case 40 ... 90: return NetworkDeviceState::Connecting; - case 100: return NetworkDeviceState::Connected; - case 110 ... 120: return NetworkDeviceState::Disconnecting; - } -} - } // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp index 147bbf30..3aa4ae40 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -11,82 +11,10 @@ #include #include "../../dbus/properties.hpp" -#include "../frontend.hpp" #include "connection.hpp" +#include "enums.hpp" #include "nm/dbus_nm_device.h" -namespace qs::network { - -class NMDeviceState: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unkown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); - - static NetworkDeviceState::Enum toNetworkDeviceState(NMDeviceState::Enum state); -}; - -class NMDeviceType: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Generic = 14, - Ethernet = 1, - Wifi = 2, - Unused1 = 3, - Unused2 = 4, - Bluetooth = 5, - OlpcMesh = 6, - Wimax = 7, - Modem = 8, - Infiniband = 9, - Bond = 10, - Vlan = 11, - Adsl = 12, - Bridge = 13, - Team = 15, - Tun = 16, - Tunnel = 17, - Macvlan = 18, - Vxlan = 19, - Veth = 20, - Macsec = 21, - Dummy = 22, - Ppp = 23, - OvsInterface = 24, - OvsPort = 25, - OvsBridge = 26, - Wpan = 27, - Lowpan = 28, - WireGuard = 29, - WifiP2P = 30, - Vrf = 31, - Loopback = 32, - Hsr = 33, - Ipvlan = 34, - }; - Q_ENUM(Enum); -}; - -} // namespace qs::network - namespace qs::dbus { template <> @@ -119,20 +47,22 @@ class NMDeviceAdapter: public QObject { [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; - [[nodiscard]] QString getInterface() { return this->bInterface; }; - [[nodiscard]] QString getHwAddress() { return this->bHwAddress; }; - [[nodiscard]] NMDeviceType::Enum getType() { return this->bType; }; + [[nodiscard]] QString interface() { return this->bInterface; }; + [[nodiscard]] QString hwAddress() { return this->bHwAddress; }; + [[nodiscard]] NMDeviceType::Enum type() { return this->bType; }; + [[nodiscard]] NMDeviceState::Enum state() { return this->bState; }; public slots: void disconnect(); signals: + void ready(); + void connectionLoaded(NMConnectionAdapter* connection); + void connectionRemoved(NMConnectionAdapter* connection); void interfaceChanged(const QString& interface); void hwAddressChanged(const QString& hwAddress); void typeChanged(NMDeviceType::Enum type); void stateChanged(NMDeviceState::Enum state); - void connectionAdded(const QString& path); - void connectionRemoved(const QString& path); void availableConnectionsChanged(QList paths); private slots: diff --git a/src/network/nm/enums.hpp b/src/network/nm/enums.hpp new file mode 100644 index 00000000..928cca4e --- /dev/null +++ b/src/network/nm/enums.hpp @@ -0,0 +1,202 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qs::network { + +// 802.11 specific device encryption and authentication capabilities. +class NMWirelessCapabilities: public QObject { + Q_OBJECT; + +public: + enum Enum : quint16 { + None = 0, + CipherWep40 = 1, + CipherWep104 = 2, + CipherTkip = 4, + CipherCcmp = 8, + Wpa = 16, + Rsn = 32, + Ap = 64, + Adhoc = 128, + FreqValid = 256, + Freq2Ghz = 512, + Freq5Ghz = 1024, + Freq6Ghz = 2048, + Mesh = 4096, + IbssRsn = 8192, + }; + Q_ENUM(Enum); +}; + +class NMWirelessSecurityType: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Wpa3SuiteB192 = 0, + Sae = 1, + Wpa2Eap = 2, + Wpa2Psk = 3, + WpaEap = 4, + WpaPsk = 5, + StaticWep = 6, + DynamicWep = 7, + Leap = 8, + Owe = 9, + None = 10, + Unknown = 11, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NMWirelessSecurityType::Enum type) { + switch (type) { + case Wpa3SuiteB192: return "WPA3 Suite B 192-bit"; + case Sae: return "WPA3"; + case Wpa2Eap: return "WPA2 Enterprise"; + case Wpa2Psk: return "WPA2"; + case WpaEap: return "WPA Enterprise"; + case WpaPsk: return "WPA"; + case StaticWep: return "WEP"; + case DynamicWep: return "Dynamic WEP"; + case Leap: return "LEAP"; + case Owe: return "OWE"; + case None: return "None"; + default: return "Unknown"; + } + } +}; + +class NMDeviceState: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unkown = 0, + Unmanaged = 10, + Unavailable = 20, + Disconnected = 30, + Prepare = 40, + Config = 50, + NeedAuth = 60, + IPConfig = 70, + IPCheck = 80, + Secondaries = 90, + Activated = 100, + Deactivating = 110, + Failed = 120, + }; + Q_ENUM(Enum); +}; + +// Indicates the type of hardware represented by a device object +class NMDeviceType: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unknown = 0, + Generic = 14, + Ethernet = 1, + Wifi = 2, + Unused1 = 3, + Unused2 = 4, + Bluetooth = 5, + OlpcMesh = 6, + Wimax = 7, + Modem = 8, + Infiniband = 9, + Bond = 10, + Vlan = 11, + Adsl = 12, + Bridge = 13, + Team = 15, + Tun = 16, + Tunnel = 17, + Macvlan = 18, + Vxlan = 19, + Veth = 20, + Macsec = 21, + Dummy = 22, + Ppp = 23, + OvsInterface = 24, + OvsPort = 25, + OvsBridge = 26, + Wpan = 27, + Lowpan = 28, + WireGuard = 29, + WifiP2P = 30, + Vrf = 31, + Loopback = 32, + Hsr = 33, + Ipvlan = 34, + }; + Q_ENUM(Enum); +}; + +// Indicates the 802.11 mode an access point is currently in. +class NM80211Mode: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + Unknown = 0, + Adhoc = 1, + Infra = 2, + Ap = 3, + Mesh = 3, + }; + Q_ENUM(Enum); +}; + +// 802.11 access point flags. +class NM80211ApFlags: public QObject { + Q_OBJECT; + +public: + enum Enum : quint8 { + None = 0, + Privacy = 1, + Wps = 2, + WpsPbc = 4, + WpsPin = 8, + }; + Q_ENUM(Enum); +}; + +// 802.11 access point security and authentication flags. +// These flags describe the current system requirements of an access point as determined from the access point's beacon. +class NM80211ApSecurityFlags: public QObject { + Q_OBJECT; + +public: + enum Enum : quint16 { + None = 0, + PairWep40 = 1, + PairWep104 = 2, + PairTkip = 4, + PairCcmp = 8, + GroupWep40 = 16, + GroupWep104 = 32, + GroupTkip = 64, + GroupCcmp = 128, + KeyMgmtPsk = 256, + KeyMgmt8021x = 512, + KeyMgmtSae = 1024, + KeyMgmtOwe = 2048, + KeyMgmtOweTm = 4096, + KeyMgmtEapSuiteB192 = 8192, + }; + Q_ENUM(Enum); +}; + +} // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml index c3f829c3..a27e8780 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml @@ -6,5 +6,6 @@ + diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml index 28b505ef..dfe3cae6 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml @@ -2,12 +2,12 @@ - + - + diff --git a/src/network/nm/utils.cpp b/src/network/nm/utils.cpp new file mode 100644 index 00000000..1a62540d --- /dev/null +++ b/src/network/nm/utils.cpp @@ -0,0 +1,342 @@ +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dbus_types.hpp" +#include "enums.hpp" + +namespace qs::network { + +NMWirelessSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) { + const QVariantMap& security = settings.value("802-11-wireless-security"); + if (security.isEmpty()) { + return NMWirelessSecurityType::Unknown; + }; + + QString keyMgmt = security["key-mgmt"].toString(); + QString authAlg = security["auth-alg"].toString(); + QList proto = security["proto"].toList(); + + if (keyMgmt == "none") { + return NMWirelessSecurityType::StaticWep; + } else if (keyMgmt == "ieee8021x") { + if (authAlg == "leap") { + return NMWirelessSecurityType::Leap; + } else { + return NMWirelessSecurityType::DynamicWep; + } + } else if (keyMgmt == "wpa-psk") { + if (proto.contains("wpa") && proto.contains("rsn")) { + return NMWirelessSecurityType::WpaPsk; + } + return NMWirelessSecurityType::Wpa2Psk; + } else if (keyMgmt == "wpa-eap") { + if (proto.contains("wpa") && proto.contains("rsn")) { + return NMWirelessSecurityType::WpaEap; + } + return NMWirelessSecurityType::Wpa2Eap; + } else if (keyMgmt == "sae") { + return NMWirelessSecurityType::Sae; + } else if (keyMgmt == "wpa-eap-suite-b-192") { + return NMWirelessSecurityType::Wpa3SuiteB192; + } + + return NMWirelessSecurityType::None; +} + +bool deviceSupportsApCiphers( + NMWirelessCapabilities::Enum caps, + NM80211ApSecurityFlags::Enum apFlags, + NMWirelessSecurityType::Enum type +) { + bool havePair = false; + bool haveGroup = false; + // Device needs to support at least one pairwise and one group cipher + + if (type == NMWirelessSecurityType::StaticWep) { + // Static WEP only uses group ciphers + havePair = true; + } else { + if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::PairWep40) { + havePair = true; + } + if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::PairWep104) + { + havePair = true; + } + if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::PairTkip) { + havePair = true; + } + if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::PairCcmp) { + havePair = true; + } + } + + if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::GroupWep40) { + haveGroup = true; + } + if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::GroupWep104) + { + haveGroup = true; + } + if (type == NMWirelessSecurityType::StaticWep) { + if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::GroupTkip) { + haveGroup = true; + } + if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::GroupCcmp) { + haveGroup = true; + } + } + + return (havePair && haveGroup); +} + +// In sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid() +// Given a set of device capabilities, and a desired security type to check +// against, determines whether the combination of device, desired security type, +// and AP capabilities intersect. +bool securityIsValid( + NMWirelessSecurityType::Enum type, + NMWirelessCapabilities::Enum caps, + bool haveAp, + bool adhoc, + NM80211ApFlags::Enum apFlags, + NM80211ApSecurityFlags::Enum apWpa, + NM80211ApSecurityFlags::Enum apRsn +) { + bool good = true; + + if (!haveAp) { + if (type == NMWirelessSecurityType::None) { + return true; + } + if ((type == NMWirelessSecurityType::StaticWep) + || ((type == NMWirelessSecurityType::DynamicWep) && !adhoc) + || ((type == NMWirelessSecurityType::Leap) && !adhoc)) + { + return caps & NMWirelessCapabilities::CipherWep40 + || caps & NMWirelessCapabilities::CipherWep104; + } + } + + switch (type) { + case NMWirelessSecurityType::None: + if (apFlags & NM80211ApFlags::Privacy) { + return false; + } + if (apWpa || apRsn) { + return false; + } + break; + case NMWirelessSecurityType::Leap: + if (adhoc) { + return false; + } + case NMWirelessSecurityType::StaticWep: + if (!(apFlags & NM80211ApFlags::Privacy)) { + return false; + } + if (apWpa || apRsn) { + if (!deviceSupportsApCiphers(caps, apWpa, NMWirelessSecurityType::StaticWep)) { + if (!deviceSupportsApCiphers(caps, apRsn, NMWirelessSecurityType::StaticWep)) { + return false; + } + } + } + break; + case NMWirelessSecurityType::DynamicWep: + if (adhoc) { + return false; + } + if (apRsn || !(apFlags & NM80211ApFlags::Privacy)) { + return false; + } + if (apWpa) { + if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) { + return false; + } + if (!deviceSupportsApCiphers(caps, apWpa, NMWirelessSecurityType::DynamicWep)) { + return false; + } + } + break; + case NMWirelessSecurityType::WpaPsk: + if (adhoc) { + return false; + } + + if (!(caps & NMWirelessCapabilities::Wpa)) { + return false; + } + if (haveAp) { + if (apWpa & NM80211ApSecurityFlags::KeyMgmtPsk) { + if (apWpa & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { + return true; + } + if (apWpa & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { + return true; + } + } + return false; + } + break; + case NMWirelessSecurityType::Wpa2Psk: + if (!(caps & NMWirelessCapabilities::Rsn)) { + return false; + } + if (haveAp) { + if (adhoc) { + if (!(caps & NMWirelessCapabilities::IbssRsn)) { + return false; + } + if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { + return true; + } + } else { + if (apRsn & NM80211ApSecurityFlags::KeyMgmtPsk) { + if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) + { + return true; + } + if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) + { + return true; + } + } + } + return false; + } + break; + case NMWirelessSecurityType::WpaEap: + if (adhoc) { + return false; + } + if (!(caps & NMWirelessCapabilities::Wpa)) { + return false; + } + if (haveAp) { + if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) { + return false; + } + // Ensure at least one WPA cipher is supported + if (!deviceSupportsApCiphers(caps, apWpa, NMWirelessSecurityType::WpaEap)) { + return false; + } + } + break; + case NMWirelessSecurityType::Wpa2Eap: + if (adhoc) { + return false; + } + if (!(caps & NMWirelessCapabilities::Rsn)) { + return false; + } + if (haveAp) { + if (!(apRsn & NM80211ApSecurityFlags::KeyMgmt8021x)) { + return false; + } + // Ensure at least one WPA cipher is supported + if (!deviceSupportsApCiphers(caps, apRsn, NMWirelessSecurityType::Wpa2Eap)) { + return false; + } + } + break; + case NMWirelessSecurityType::Sae: + if (!(caps & NMWirelessCapabilities::Rsn)) { + return false; + } + if (haveAp) { + if (adhoc) { + if (!(caps & NMWirelessCapabilities::IbssRsn)) { + return false; + } + if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { + return true; + } + } else { + if (apRsn & NM80211ApSecurityFlags::KeyMgmtSae) { + if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) + { + return true; + } + if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) + { + return true; + } + } + } + return false; + } + break; + case NMWirelessSecurityType::Owe: + if (adhoc) { + return false; + } + if (!(caps & NMWirelessCapabilities::Rsn)) { + return false; + } + if (haveAp) { + if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtOwe) + && !(apRsn & NM80211ApSecurityFlags::KeyMgmtOweTm)) + { + return false; + } + } + break; + case NMWirelessSecurityType::Wpa3SuiteB192: + if (adhoc) { + return false; + } + if (!(caps & NMWirelessCapabilities::Rsn)) { + return false; + } + if (haveAp && !(apRsn & NM80211ApSecurityFlags::KeyMgmtEapSuiteB192)) { + return false; + } + break; + default: good = false; break; + } + + return good; +} + +NMWirelessSecurityType::Enum findBestWirelessSecurity( + NMWirelessCapabilities::Enum caps, + bool haveAp, + bool adHoc, + NM80211ApFlags::Enum apFlags, + NM80211ApSecurityFlags::Enum apWpa, + NM80211ApSecurityFlags::Enum apRsn +) { + // The ordering of this list is a pragmatic combination of security level and popularity + const QList types = { + NMWirelessSecurityType::Wpa3SuiteB192, + NMWirelessSecurityType::Sae, + NMWirelessSecurityType::Wpa2Eap, + NMWirelessSecurityType::Wpa2Psk, + NMWirelessSecurityType::WpaEap, + NMWirelessSecurityType::WpaPsk, + NMWirelessSecurityType::StaticWep, + NMWirelessSecurityType::DynamicWep, + NMWirelessSecurityType::Leap, + NMWirelessSecurityType::Owe, + NMWirelessSecurityType::None + }; + + for (NMWirelessSecurityType::Enum type: types) { + if (securityIsValid(type, caps, haveAp, adHoc, apFlags, apWpa, apRsn)) { + return type; + } + } + return NMWirelessSecurityType::Unknown; +} + +} // namespace qs::network diff --git a/src/network/nm/utils.hpp b/src/network/nm/utils.hpp new file mode 100644 index 00000000..7cdcc0b8 --- /dev/null +++ b/src/network/nm/utils.hpp @@ -0,0 +1,46 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dbus_types.hpp" +#include "enums.hpp" + +namespace qs::network { + +NMWirelessSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings); + +bool deviceSupportsApCiphers( + NMWirelessCapabilities::Enum caps, + NM80211ApSecurityFlags::Enum apFlags, + NMWirelessSecurityType::Enum type +); + +bool securityIsValid( + NMWirelessSecurityType::Enum type, + NMWirelessCapabilities::Enum caps, + bool haveAp, + bool adhoc, + NM80211ApFlags::Enum apFlags, + NM80211ApSecurityFlags::Enum apWpa, + NM80211ApSecurityFlags::Enum apRsn +); + +NMWirelessSecurityType::Enum findBestWirelessSecurity( + NMWirelessCapabilities::Enum caps, + bool haveAp, + bool adHoc, + NM80211ApFlags::Enum apFlags, + NM80211ApSecurityFlags::Enum apWpa, + NM80211ApSecurityFlags::Enum apRsn +); + +} // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp index 46de20ef..10c74499 100644 --- a/src/network/nm/wireless.cpp +++ b/src/network/nm/wireless.cpp @@ -8,17 +8,97 @@ #include #include -#include "../../dbus/properties.hpp" -#include "nm/dbus_nm_wireless.h" +#include "dbus_types.hpp" +#include "nm/enums.hpp" +#include "utils.hpp" -using namespace qs::dbus; +namespace qs::dbus { + +DBusResult +DBusDataTransform::fromWire(quint32 wire) { + return DBusResult(static_cast(wire)); +} + +} // namespace qs::dbus namespace qs::network { +using namespace qs::dbus; namespace { Q_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } +NMWirelessNetwork::NMWirelessNetwork( + NMWirelessCapabilities::Enum caps, + QByteArray ssid, + QObject* parent +) + : QObject(parent) + , mDeviceCapabilities(caps) + , mSsid(std::move(ssid)) {} + +void NMWirelessNetwork::updateSignalStrength() { + quint8 max = 0; + NMAccessPointAdapter* maxAP = nullptr; + for (auto* ap: mAccessPoints) { + quint8 signal = ap->signalStrength(); + if (signal > max) { + max = signal; + maxAP = ap; + } + } + if (this->bMaxSignal != max) { + this->bMaxSignal = max; + } + if (maxAP && this->mReferenceAP != maxAP) { + this->mReferenceAP = maxAP; + emit referenceAccessPointChanged(maxAP); + } +} + +void NMWirelessNetwork::addAccessPoint(NMAccessPointAdapter* ap) { + if (this->mAccessPoints.contains(ap)) { + qCWarning(logNetworkManager) << "Access point" << ap->path() << "was already in WifiNetwork" + << this; + return; + } + + this->mAccessPoints.append(ap); + if (this->mReferenceAP == nullptr) { + this->mReferenceAP = ap; + emit referenceAccessPointChanged(ap); + } + QObject::connect( + ap, + &NMAccessPointAdapter::signalStrengthChanged, + this, + &NMWirelessNetwork::updateSignalStrength + ); + this->updateSignalStrength(); +} + +void NMWirelessNetwork::removeAccessPoint(NMAccessPointAdapter* ap) { + if (mAccessPoints.removeOne(ap)) { + QObject::disconnect(ap, nullptr, this, nullptr); + this->updateSignalStrength(); + } +} + +NMWirelessSecurityType::Enum NMWirelessNetwork::findBestSecurity() { + if (this->mReferenceAP) { + auto* ap = this->mReferenceAP; + return findBestWirelessSecurity( + this->mDeviceCapabilities, + true, + ap->mode() == NM80211Mode::Adhoc, + ap->flags(), + ap->wpaFlags(), + ap->rsnFlags() + ); + } + return NMWirelessSecurityType::Unknown; +} + NMWirelessAdapter::NMWirelessAdapter(const QString& path, QObject* parent): QObject(parent) { this->proxy = new DBusNMWirelessProxy( "org.freedesktop.NetworkManager", @@ -32,30 +112,25 @@ NMWirelessAdapter::NMWirelessAdapter(const QString& path, QObject* parent): QObj return; } + // We need the properties available to successfully register access points. + // Their security type is dependent on this adapters capabilities. QObject::connect( - this->proxy, - &DBusNMWirelessProxy::AccessPointAdded, + &this->wirelessProperties, + &DBusPropertyGroup::getAllFinished, this, - &NMWirelessAdapter::onAccessPointAdded - ); - - QObject::connect( - this->proxy, - &DBusNMWirelessProxy::AccessPointRemoved, - this, - &NMWirelessAdapter::onAccessPointRemoved - ); - - QObject::connect( - this, - &NMWirelessAdapter::activeApChanged, - this, - &NMWirelessAdapter::onActiveApChanged + [this]() { this->init(); }, + Qt::SingleShotConnection ); this->wirelessProperties.setInterface(this->proxy); this->wirelessProperties.updateAllViaGetAll(); +} +void NMWirelessAdapter::init() { + // clang-format off + QObject::connect(this->proxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessAdapter::onAccessPointAdded); + QObject::connect(this->proxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessAdapter::onAccessPointRemoved); + // clang-format on this->registerAccessPoints(); } @@ -72,28 +147,11 @@ void NMWirelessAdapter::onAccessPointRemoved(const QDBusObjectPath& path) { return; } + QObject::disconnect(ap, nullptr, nullptr, nullptr); removeApFromNetwork(ap); delete ap; } -void NMWirelessAdapter::onActiveApChanged(const QDBusObjectPath& path) { - // Disconnect all - for (WifiNetwork* nw: mWifiNetworkMap) { - if (nw) { - nw->setConnected(false); - }; - } - - QByteArray ssid = this->mSsidMap.value(path.path()); - if (ssid != "") { - WifiNetwork* network = this->mWifiNetworkMap.value(ssid); - if (network) { - network->setConnected(true); - return; - } - } -} - void NMWirelessAdapter::registerAccessPoints() { auto pending = this->proxy->GetAllAccessPoints(); auto* call = new QDBusPendingCallWatcher(pending, this); @@ -133,15 +191,32 @@ void NMWirelessAdapter::registerAccessPoint(const QString& path) { this->mApMap.insert(path, ap); qCDebug(logNetworkManager) << "Registered access point" << path; - // AP SSID can change + // Wait for properties + QObject::connect( + ap, + &NMAccessPointAdapter::ready, + this, + [this, ap]() { this->onAccessPointReady(ap); }, + Qt::SingleShotConnection + ); +} + +void NMWirelessAdapter::onAccessPointReady(NMAccessPointAdapter* ap) { + if (!ap->ssid().isEmpty()) { + this->addApToNetwork(ap, ap->ssid()); + } + + // The access points SSID can change/hide QObject::connect( ap, &NMAccessPointAdapter::ssidChanged, this, [this, ap](const QByteArray& ssid) { - if (ssid.isEmpty()) { // AP SSID can be hidden + if (ssid.isEmpty()) { this->removeApFromNetwork(ap); } else { + qCDebug(logNetworkManager) + << "Access point" << ap->path() << "changed to ssid" << ap->ssid(); this->addApToNetwork(ap, ssid); } } @@ -149,61 +224,199 @@ void NMWirelessAdapter::registerAccessPoint(const QString& path) { } void NMWirelessAdapter::addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid) { - // Remove AP from old network + // Remove AP from previous network removeApFromNetwork(ap); - auto* group = mApGroupMap.value(ssid); - if (!group) { - auto* network = new WifiNetwork(this); - group = new NMWifiNetwork(ssid, this); - network->setSsid(QString::fromUtf8(ssid)); - - // NMAccessPointGroup signal -> NetworkWifiNetwork slot - QObject::connect( - group, - &NMWifiNetwork::signalStrengthChanged, - network, - &WifiNetwork::setSignalStrength - ); - - this->mApGroupMap.insert(ssid, group); - this->mWifiNetworkMap.insert(ssid, network); - - // Sometimes active AP changes before wifi network is registered - if (this->activeApPath().path() == ap->path()) { - network->setConnected(true); - }; - - emit this->wifiNetworkAdded(network); + auto* network = mNetworkMap.value(ssid); + if (!network) { + network = new NMWirelessNetwork(this->capabilities(), ssid, this); + network->addAccessPoint(ap); + this->mNetworkMap.insert(ssid, network); + this->mSsidMap.insert(ap->path(), ssid); + emit this->networkAdded(network); + } else { + network->addAccessPoint(ap); + this->mSsidMap.insert(ap->path(), ssid); } - - group->addAccessPoint(ap); - this->mSsidMap.insert(ap->path(), ssid); } void NMWirelessAdapter::removeApFromNetwork(NMAccessPointAdapter* ap) { QByteArray ssid = mSsidMap.take(ap->path()); - if (ssid.isEmpty()) return; // AP wasn't previously associated with a wifi network + if (ssid.isEmpty()) return; // AP wasn't previously associated with a network - auto* group = mApGroupMap.value(ssid); - group->removeAccessPoint(ap); + auto* network = mNetworkMap.value(ssid); + network->removeAccessPoint(ap); - // No access points exist for the wifi network - if (group->isEmpty()) { - mApGroupMap.remove(ssid); - auto* network = mWifiNetworkMap.take(ssid); - emit wifiNetworkRemoved(network); + // No access points remain for the wifi network + if (network->isEmpty()) { + mNetworkMap.remove(ssid); + emit networkRemoved(network); delete network; - delete group; } } void NMWirelessAdapter::scan() { this->proxy->RequestScan({}); } - bool NMWirelessAdapter::isValid() const { return this->proxy && this->proxy->isValid(); } QString NMWirelessAdapter::address() const { return this->proxy ? this->proxy->service() : QString(); } QString NMWirelessAdapter::path() const { return this->proxy ? this->proxy->path() : QString(); } +NMWirelessManager::NMWirelessManager(QObject* parent): QObject(parent) {}; + +void NMWirelessManager::connectionLoaded(NMConnectionAdapter* connection) { + ConnectionSettingsMap settings = connection->settings(); + if (settings["connection"]["id"].toString().isEmpty() + || settings["connection"]["uuid"].toString().isEmpty() + || !settings.contains("802-11-wireless") + || settings["802-11-wireless"]["mode"].toString() == "ap") // Can't be devices own hotspot + { + return; + } + + // Check if this connection already supplies an item + QString uuid = settings["connection"]["uuid"].toString(); + if (this->mConnectionMap.contains(uuid)) { + return; + qCWarning(logNetworkManager) << "Wireless manager out of sync"; + }; + mConnectionMap.insert(uuid, connection); + + // Is there an item with only a network supplying it? + QString ssid = settings["802-11-wireless"]["ssid"].toString(); + for (WifiNetwork* item: mSsidToItem.values(ssid)) { + if (!item->property("saved").toBool()) { + // Supply existing item + item->setKnown(true); + item->setNmSecurity(securityFromConnectionSettings(settings)); + mUuidToItem.insert(uuid, item); + return; + } + } + + // Create a new item and supply it with only the connection + auto* item = new WifiNetwork(this); + item->setSsid(ssid); + item->setKnown(true); + item->setNmSecurity(securityFromConnectionSettings(settings)); + mUuidToItem.insert(uuid, item); + emit wifiNetworkAdded(item); + + // Is there a network that can supply this item? + auto* network = mNetworkMap.value(ssid); + if (network) { + item->setSignalStrength(network->signalStrength()); + QObject::connect( + network, + &NMWirelessNetwork::signalStrengthChanged, + item, + &WifiNetwork::setSignalStrength + ); + mSsidToItem.insert(ssid, item); + } +}; + +void NMWirelessManager::connectionRemoved(NMConnectionAdapter* connection) { + ConnectionSettingsMap settings = connection->settings(); + + // Check if this connection supplies an item + QString uuid = settings["connection"]["uuid"].toString(); + if (!this->mConnectionMap.contains(uuid)) { + return; + qCWarning(logNetworkManager) << "Wireless manager out of sync"; + } + mConnectionMap.take(uuid); + + // Does the item this connectoin supplies also have a network supplying it? + auto* item = mUuidToItem.take(uuid); + QString ssid = settings["802-11-wireless"]["ssid"].toString(); + if (mSsidToItem.contains(ssid)) { + // Is the network supplying multiple items? + if (mSsidToItem.count(ssid) != 1) { + mSsidToItem.remove(ssid, item); + emit wifiNetworkRemoved(item); + delete item; + } else { + // Keep the item and supply only from network + item->setKnown(false); + auto* network = mNetworkMap.value(ssid); + item->setNmSecurity(network->findBestSecurity()); + } + return; + } + + // Delete item + emit wifiNetworkRemoved(item); + delete item; +}; + +void NMWirelessManager::networkAdded(NMWirelessNetwork* network) { + QString ssid = network->ssid(); + if (this->mNetworkMap.contains(ssid)) { + return; + }; + mNetworkMap.insert(ssid, network); + + // Find all existing items with a connection supplier + quint8 count = 0; + for (WifiNetwork* item: mUuidToItem.values()) { + if (item->property("ssid").toString() == ssid) { + count += 1; + item->setSignalStrength(network->signalStrength()); + QObject::connect( + network, + &NMWirelessNetwork::signalStrengthChanged, + item, + &WifiNetwork::setSignalStrength + ); + mSsidToItem.insert(ssid, item); + } + } + + // Was there none? + if (count != 0) { + return; + } + + // Create an item and supply it with the network + auto* item = new WifiNetwork(this); + item->setSsid(ssid); + item->setSignalStrength(network->signalStrength()); + QObject::connect( + network, + &NMWirelessNetwork::signalStrengthChanged, + item, + &WifiNetwork::setSignalStrength + ); + item->setKnown(false); + item->setNmSecurity(network->findBestSecurity()); + mSsidToItem.insert(ssid, item); + emit this->wifiNetworkAdded(item); +}; + +void NMWirelessManager::networkRemoved(NMWirelessNetwork* network) { + QString ssid = network->ssid(); + if (!this->mNetworkMap.contains(ssid)) { + return; + } + mNetworkMap.take(ssid); + + // Remove network supplies from all supplied items + for (WifiNetwork* item: mSsidToItem.values(ssid)) { + // Was this item also supplied by a connection? + QString uuid = mUuidToItem.key(item); + if (!uuid.isEmpty()) { + // Supply only with connection + QObject::disconnect(network, nullptr, item, nullptr); + auto* conn = mConnectionMap.value(uuid); + item->setNmSecurity(securityFromConnectionSettings(conn->settings())); + } else { + // Delete item + emit this->wifiNetworkRemoved(item); + delete item; + } + }; + mSsidToItem.remove(ssid); +}; + } // namespace qs::network diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp index 9101537e..75fe2e6d 100644 --- a/src/network/nm/wireless.hpp +++ b/src/network/nm/wireless.hpp @@ -10,13 +10,60 @@ #include #include -#include "../../dbus/properties.hpp" #include "../frontend.hpp" #include "accesspoint.hpp" +#include "connection.hpp" +#include "enums.hpp" #include "nm/dbus_nm_wireless.h" +namespace qs::dbus { +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMWirelessCapabilities::Enum; + static DBusResult fromWire(Wire wire); +}; + +} // namespace qs::dbus namespace qs::network { +// NMWirelessNetwork represents a wireless network, which aggregates all access points with +// the same SSID. It also provides signals and slots for a frontend WifiNetwork. +class NMWirelessNetwork: public QObject { + Q_OBJECT; + +public: + explicit NMWirelessNetwork( + NMWirelessCapabilities::Enum caps, + QByteArray ssid, + QObject* parent = nullptr + ); + void addAccessPoint(NMAccessPointAdapter* ap); + void removeAccessPoint(NMAccessPointAdapter* ap); + void updateSignalStrength(); + + [[nodiscard]] bool isEmpty() const { return this->mAccessPoints.isEmpty(); }; + [[nodiscard]] quint8 signalStrength() const { return this->bMaxSignal; }; + [[nodiscard]] QString ssid() const { return this->mSsid; }; + [[nodiscard]] NMAccessPointAdapter* referenceAccessPoint() const { return this->mReferenceAP; }; + [[nodiscard]] NMWirelessSecurityType::Enum findBestSecurity(); + +signals: + void signalStrengthChanged(quint8 signal); + void referenceAccessPointChanged(NMAccessPointAdapter* ap); + void disappeared(const QString& ssid); + +private: + NMAccessPointAdapter* mReferenceAP = nullptr; + QList mAccessPoints; + NMWirelessCapabilities::Enum mDeviceCapabilities; + QByteArray mSsid; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bMaxSignal, &NMWirelessNetwork::signalStrengthChanged); + // clang-format on +}; + // NMWirelessAdapter wraps the state of a NetworkManager wireless device // (org.freedesktop.NetworkManager.Device.Wireless), provides signals/slots that connect to a // frontend NetworkWifiDevice, and creates/destroys NMAccessPointAdapters and NMAccessPointGroups @@ -26,6 +73,7 @@ class NMWirelessAdapter: public QObject { public: explicit NMWirelessAdapter(const QString& path, QObject* parent = nullptr); + void init(); void registerAccessPoint(const QString& path); void registerAccessPoints(); void addApToNetwork(NMAccessPointAdapter* ap, const QByteArray& ssid); @@ -36,6 +84,7 @@ class NMWirelessAdapter: public QObject { [[nodiscard]] QString address() const; [[nodiscard]] qint64 getLastScan() { return this->bLastScan; }; [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }; + [[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; }; public slots: void scan(); @@ -43,32 +92,60 @@ public slots: signals: void lastScanChanged(qint64 lastScan); void activeApChanged(const QDBusObjectPath& path); + void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void wifiNetworkAdded(WifiNetwork* network); - void wifiNetworkRemoved(WifiNetwork* network); + void networkAdded(NMWirelessNetwork* network); + void networkRemoved(NMWirelessNetwork* network); private slots: void onAccessPointAdded(const QDBusObjectPath& path); void onAccessPointRemoved(const QDBusObjectPath& path); - void onActiveApChanged(const QDBusObjectPath& path); + void onAccessPointReady(NMAccessPointAdapter* ap); private: - // Lookups: AP <-> AP group <-> frontend wifi network - QHash mApMap; // AP Path -> NMAccessPointAdapter* - QHash mSsidMap; // AP Path -> Ssid - QHash mApGroupMap; // Ssid -> NMAccessPointGroup* - QHash mWifiNetworkMap; // Ssid -> NetworkWifiNetwork* + // Lookups: NMAccessPointAdapter <-> NMWifiNetwork + QHash mApMap; // AP Path -> NMAccessPointAdapter* + QHash mSsidMap; // AP Path -> Ssid + QHash mNetworkMap; // Ssid -> NMAccessPointGroup* // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, qint64, bLastScan, &NMWirelessAdapter::lastScanChanged); Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, QDBusObjectPath, bActiveAccessPoint, &NMWirelessAdapter::activeApChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessAdapter, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessAdapter::capabilitiesChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWirelessAdapter, wirelessProperties); QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pLastScan, bLastScan, wirelessProperties, "LastScan"); QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); + QS_DBUS_PROPERTY_BINDING(NMWirelessAdapter, pCapabilities, bCapabilities, wirelessProperties, "WirelessCapabilities"); // clang-format on DBusNMWirelessProxy* proxy = nullptr; }; +// NMWirelessManager processes the incoming/outgoing connections (NMDeviceAdapter) and wifi +// networks (NMWirelessAdapter), and merges them into WifiItems for the frontend. +class NMWirelessManager: public QObject { + Q_OBJECT; + +public: + explicit NMWirelessManager(QObject* parent = nullptr); + +public slots: + void connectionLoaded(NMConnectionAdapter* connection); + void connectionRemoved(NMConnectionAdapter* connection); + void networkAdded(NMWirelessNetwork* nmNetwork); + void networkRemoved(NMWirelessNetwork* nmNetwork); + +signals: + void wifiNetworkAdded(WifiNetwork* network); + void wifiNetworkRemoved(WifiNetwork* network); + +private: + // Lookups to help merge 1:1 connection:item relation and 1:many network:item relation. + QHash mNetworkMap; // Ssid -> NMWirelessNetwork + QHash mConnectionMap; // Uuid -> NMConnectionAdapter + QHash mUuidToItem; // Uuid -> WifiNetwork + QMultiHash mSsidToItem; // Ssid -> WifiNetwork +}; + } // namespace qs::network diff --git a/src/network/test/network.qml b/src/network/test/network.qml index 4de9391f..6f4eda87 100644 --- a/src/network/test/network.qml +++ b/src/network/test/network.qml @@ -51,9 +51,8 @@ FloatingWindow { } } Label { text: "Available networks: " } - GridLayout { - columns: 3 + columns: 4 columnSpacing: 30 rowSpacing: 5 @@ -65,12 +64,17 @@ FloatingWindow { Label { Layout.row: 0 Layout.column: 1 - text: "SIGNAL" + text: "SECURITY" } Label { Layout.row: 0 Layout.column: 2 - text: "CONNECTED" + text: "KNOWN" + } + Label { + Layout.row: 0 + Layout.column: 3 + text: "SIGNAL STRENGTH" } Repeater { @@ -86,7 +90,7 @@ FloatingWindow { delegate: Text { Layout.row: index + 1 Layout.column: 1 - text: `${modelData.signalStrength}%` + text: NMWirelessSecurityType.toString(modelData.nmSecurity) } } Repeater { @@ -94,7 +98,15 @@ FloatingWindow { delegate: Text { Layout.row: index + 1 Layout.column: 2 - text: modelData.connected ? "*" : "" + text: modelData.known ? "*" : "" + } + } + Repeater { + model: modelData.networks + delegate: Text { + Layout.row: index + 1 + Layout.column: 3 + text: modelData.signalStrength + "%" } } }