From d60498adc038526b3d155e8ad61e51e78e6e32eb Mon Sep 17 00:00:00 2001 From: Carson Powers Date: Thu, 2 Apr 2026 17:02:14 -0500 Subject: [PATCH] networking: add wired device support --- src/network/CMakeLists.txt | 2 + src/network/device.cpp | 4 + src/network/device.hpp | 19 +- src/network/enums.cpp | 1 + src/network/enums.hpp | 1 + src/network/module.md | 2 + src/network/network.cpp | 69 +-- src/network/network.hpp | 158 +----- src/network/nm/CMakeLists.txt | 13 + src/network/nm/accesspoint.hpp | 2 +- src/network/nm/backend.cpp | 75 +-- src/network/nm/backend.hpp | 5 +- src/network/nm/device.cpp | 48 ++ src/network/nm/device.hpp | 27 +- src/network/nm/enums.hpp | 17 + src/network/nm/network.cpp | 308 ++++++++++++ src/network/nm/network.hpp | 138 ++++++ ...reedesktop.NetworkManager.Device.Wired.xml | 4 + src/network/nm/settings.cpp | 1 + src/network/nm/utils.cpp | 28 +- src/network/nm/wired.cpp | 101 ++++ src/network/nm/wired.hpp | 53 ++ src/network/nm/wireless.cpp | 317 ++---------- src/network/nm/wireless.hpp | 78 +-- src/network/qml.cpp | 82 ++++ src/network/qml.hpp | 151 ++++++ src/network/test/manual/network.qml | 453 ++++++++++-------- src/network/wifi.cpp | 6 +- src/network/wifi.hpp | 13 +- src/network/wired.cpp | 22 + src/network/wired.hpp | 53 ++ 31 files changed, 1374 insertions(+), 877 deletions(-) create mode 100644 src/network/nm/network.cpp create mode 100644 src/network/nm/network.hpp create mode 100644 src/network/nm/org.freedesktop.NetworkManager.Device.Wired.xml create mode 100644 src/network/nm/wired.cpp create mode 100644 src/network/nm/wired.hpp create mode 100644 src/network/qml.cpp create mode 100644 src/network/qml.hpp create mode 100644 src/network/wired.cpp create mode 100644 src/network/wired.hpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 03ef86a..d82c0ab 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -3,8 +3,10 @@ add_subdirectory(nm) qt_add_library(quickshell-network STATIC network.cpp device.cpp + wired.cpp wifi.cpp enums.cpp + qml.cpp ) target_include_directories(quickshell-network PRIVATE diff --git a/src/network/device.cpp b/src/network/device.cpp index 5679e8d..7abe971 100644 --- a/src/network/device.cpp +++ b/src/network/device.cpp @@ -9,6 +9,7 @@ #include "../core/logcat.hpp" #include "enums.hpp" +#include "network.hpp" namespace qs::network { @@ -45,4 +46,7 @@ void NetworkDevice::disconnect() { this->requestDisconnect(); } +void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); } +void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); } + } // namespace qs::network diff --git a/src/network/device.hpp b/src/network/device.hpp index 8d914a1..385d2ba 100644 --- a/src/network/device.hpp +++ b/src/network/device.hpp @@ -7,12 +7,14 @@ #include #include "../core/doc.hpp" +#include "../core/model.hpp" #include "enums.hpp" +#include "network.hpp" namespace qs::network { ///! A network device. -/// The @@type property may be used to determine if this device is a @@WifiDevice. +/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice. class NetworkDevice: public QObject { Q_OBJECT; QML_ELEMENT; @@ -20,11 +22,17 @@ class NetworkDevice: public QObject { // clang-format off /// The device type. /// - /// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network + /// When the device type is `Wifi`, the device object is a @@WifiDevice. + /// When the device type is `Wired`, the device object is a @@WiredDevice. /// connection and scanning. Q_PROPERTY(DeviceType::Enum type READ type CONSTANT); /// The name of the device's control interface. Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName); + /// A list of available or connected networks for this device. + /// + /// When the device type is 'Wifi', this model will only contain @@WifiNetwork. + QSDOC_TYPE_OVERRIDE(ObjectModel*); + Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); /// The hardware address of the device in the XX:XX:XX:XX:XX:XX format. Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); /// True if the device is connected. @@ -45,6 +53,10 @@ public: /// Disconnects the device and prevents it from automatically activating further connections. Q_INVOKABLE void disconnect(); + virtual void networkAdded(Network* net); + virtual void networkRemoved(Network* net); + + [[nodiscard]] ObjectModel* networks() { return &this->mNetworks; } [[nodiscard]] DeviceType::Enum type() const { return this->mType; } QBindable bindableName() { return &this->bName; } [[nodiscard]] QString name() const { return this->bName; } @@ -69,6 +81,9 @@ signals: void nmManagedChanged(); void autoconnectChanged(); +protected: + ObjectModel mNetworks {this}; + private: DeviceType::Enum mType; // clang-format off diff --git a/src/network/enums.cpp b/src/network/enums.cpp index 2cf36c1..bdbacb3 100644 --- a/src/network/enums.cpp +++ b/src/network/enums.cpp @@ -50,6 +50,7 @@ QString DeviceType::toString(DeviceType::Enum type) { switch (type) { case None: return QStringLiteral("None"); case Wifi: return QStringLiteral("Wifi"); + case Wired: return QStringLiteral("Wired"); default: return QStringLiteral("Unknown"); } } diff --git a/src/network/enums.hpp b/src/network/enums.hpp index 49c28ce..2576476 100644 --- a/src/network/enums.hpp +++ b/src/network/enums.hpp @@ -98,6 +98,7 @@ public: enum Enum : quint8 { None = 0, Wifi = 1, + Wired = 2, }; Q_ENUM(Enum); Q_INVOKABLE static QString toString(DeviceType::Enum type); diff --git a/src/network/module.md b/src/network/module.md index 91ff2f1..cd314d9 100644 --- a/src/network/module.md +++ b/src/network/module.md @@ -1,9 +1,11 @@ name = "Quickshell.Networking" description = "Network API" headers = [ + "qml.hpp", "network.hpp", "device.hpp", "wifi.hpp", + "wired.hpp", "enums.hpp", "nm/settings.hpp", ] diff --git a/src/network/network.cpp b/src/network/network.cpp index e66ffa6..1bcd9ff 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -6,12 +6,9 @@ #include #include #include -#include #include "../core/logcat.hpp" -#include "device.hpp" #include "enums.hpp" -#include "nm/backend.hpp" #include "nm/settings.hpp" namespace qs::network { @@ -20,68 +17,10 @@ namespace { QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); } // namespace -Networking::Networking(QObject* parent): QObject(parent) { - // Try to create the NetworkManager backend and bind to it. - auto* nm = new NetworkManager(this); - if (nm->isAvailable()) { - // clang-format off - QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded); - QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved); - QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled); - QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled); - QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity); - this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); }); - this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); }); - this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); }); - this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); }); - this->bindableConnectivity().setBinding([nm]() { return static_cast(nm->connectivity()); }); - // clang-format on - - this->mBackend = nm; - this->mBackendType = NetworkBackendType::NetworkManager; - return; - } else { - delete nm; - } - qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; -} - -Networking* Networking::instance() { - static Networking* instance = new Networking(); // NOLINT - return instance; -} - -void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); } -void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); } - -void Networking::checkConnectivity() { - if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return; - emit this->requestCheckConnectivity(); -} - -void Networking::setWifiEnabled(bool enabled) { - if (this->bWifiEnabled == enabled) return; - emit this->requestSetWifiEnabled(enabled); -} - -void Networking::setConnectivityCheckEnabled(bool enabled) { - if (this->bConnectivityCheckEnabled == enabled) return; - emit this->requestSetConnectivityCheckEnabled(enabled); -} - -NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) { - // clang-format off - QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged); - QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged); - QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged); - QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged); - QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged); - // clang-format on -} - -void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); } - -Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) { +Network::Network(QString name, NetworkDevice* device, QObject* parent) + : QObject(parent) + , bName(std::move(name)) + , mDevice(device) { this->bStateChanging.setBinding([this] { auto state = this->bState.value(); return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting; diff --git a/src/network/network.hpp b/src/network/network.hpp index f7734a2..9022cc7 100644 --- a/src/network/network.hpp +++ b/src/network/network.hpp @@ -7,147 +7,14 @@ #include #include "../core/doc.hpp" -#include "../core/model.hpp" -#include "device.hpp" #include "enums.hpp" #include "nm/settings.hpp" namespace qs::network { +class NetworkDevice; +} -class NetworkBackend: public QObject { - Q_OBJECT; - -public: - [[nodiscard]] virtual bool isAvailable() const = 0; - -protected: - explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; -}; - -class Networking: public QObject { - Q_OBJECT; - -public: - static Networking* instance(); - - void checkConnectivity(); - - [[nodiscard]] ObjectModel* devices() { return &this->mDevices; } - [[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; } - QBindable bindableWifiEnabled() { return &this->bWifiEnabled; } - [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; } - void setWifiEnabled(bool enabled); - QBindable bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; } - QBindable bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; } - QBindable bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; } - [[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; } - void setConnectivityCheckEnabled(bool enabled); - QBindable bindableConnectivity() { return &this->bConnectivity; } - -signals: - void requestSetWifiEnabled(bool enabled); - void requestSetConnectivityCheckEnabled(bool enabled); - void requestCheckConnectivity(); - - void wifiEnabledChanged(); - void wifiHardwareEnabledChanged(); - void canCheckConnectivityChanged(); - void connectivityCheckEnabledChanged(); - void connectivityChanged(); - -private slots: - void deviceAdded(NetworkDevice* dev); - void deviceRemoved(NetworkDevice* dev); - -private: - explicit Networking(QObject* parent = nullptr); - - ObjectModel mDevices {this}; - NetworkBackend* mBackend = nullptr; - NetworkBackendType::Enum mBackendType = NetworkBackendType::None; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged); - // clang-format on -}; - -///! The Network service. -/// An interface to a network backend (currently only NetworkManager), -/// which can be used to view, configure, and connect to various networks. -class NetworkingQml: public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(Networking); - QML_SINGLETON; - // clang-format off - /// A list of all network devices. Networks are exposed through their respective devices. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); - /// The backend being used to power the Network service. - Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT); - /// Switch for the rfkill software block of all wireless devices. - Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged); - /// State of the rfkill hardware block of all wireless devices. - Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled); - /// True if the @@backend supports connectivity checks. - Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity); - /// True if connectivity checking is enabled. - Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged); - /// The result of the last connectivity check. - /// - /// Connectivity checks may require additional configuration depending on your distro. - /// - /// > [!NOTE] This property can be used to determine if network access is restricted - /// > or gated behind a captive portal. - /// > - /// > If checking for captive portals, @@checkConnectivity() should be called after - /// > the portal is dismissed to update this property. - Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity); - // clang-format on - -public: - explicit NetworkingQml(QObject* parent = nullptr); - - /// Re-check the network connectivity state immediately. - /// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal. - Q_INVOKABLE static void checkConnectivity(); - - [[nodiscard]] static ObjectModel* devices() { - return Networking::instance()->devices(); - } - [[nodiscard]] static NetworkBackendType::Enum backend() { - return Networking::instance()->backend(); - } - [[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); } - static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); } - [[nodiscard]] static QBindable bindableWifiHardwareEnabled() { - return Networking::instance()->bindableWifiHardwareEnabled(); - } - [[nodiscard]] static QBindable bindableWifiEnabled() { - return Networking::instance()->bindableWifiEnabled(); - } - [[nodiscard]] static QBindable bindableCanCheckConnectivity() { - return Networking::instance()->bindableCanCheckConnectivity(); - } - [[nodiscard]] static bool connectivityCheckEnabled() { - return Networking::instance()->connectivityCheckEnabled(); - } - static void setConnectivityCheckEnabled(bool enabled) { - Networking::instance()->setConnectivityCheckEnabled(enabled); - } - [[nodiscard]] static QBindable bindableConnectivity() { - return Networking::instance()->bindableConnectivity(); - } - -signals: - void wifiEnabledChanged(); - void wifiHardwareEnabledChanged(); - void canCheckConnectivityChanged(); - void connectivityCheckEnabledChanged(); - void connectivityChanged(); -}; +namespace qs::network { ///! A network. /// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances. @@ -158,11 +25,13 @@ class Network: public QObject { // clang-format off /// The name of the network. - Q_PROPERTY(QString name READ name CONSTANT); + Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); + /// The device this network belongs to. + Q_PROPERTY(NetworkDevice* device READ device CONSTANT); /// A list of NetworkManager connnection settings profiles for this network. /// /// > [!WARNING] Only valid for the NetworkManager backend. - Q_PROPERTY(QList nmSettings READ nmSettings NOTIFY nmSettingsChanged BINDABLE bindableNmSettings); + Q_PROPERTY(QList nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings); /// True if the network is connected. Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); /// True if the wifi network has known connection settings saved. @@ -174,7 +43,7 @@ class Network: public QObject { // clang-format on public: - explicit Network(QString name, QObject* parent = nullptr); + explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr); /// Attempt to connect to the network. /// /// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s) @@ -194,7 +63,9 @@ public: void settingsRemoved(NMSettings* settings); // clang-format off - [[nodiscard]] QString name() const { return this->mName; } + [[nodiscard]] QString name() const { return this->bName; } + [[nodiscard]] QBindable bindableName() { return &this->bName; } + [[nodiscard]] NetworkDevice* device() const { return this->mDevice; } [[nodiscard]] const QList& nmSettings() const { return this->bNmSettings; } QBindable> bindableNmSettings() const { return &this->bNmSettings; } QBindable bindableConnected() { return &this->bConnected; } @@ -208,6 +79,7 @@ signals: /// Signals that a connection to the network has failed because of the given @@ConnectionFailReason. void connectionFailed(ConnectionFailReason::Enum reason); + void nameChanged(); void connectedChanged(); void knownChanged(); void stateChanged(); @@ -219,15 +91,17 @@ signals: QSDOC_HIDE void requestForget(); protected: - QString mName; - // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, QList, bNmSettings, &Network::nmSettingsChanged); // clang-format on + +private: + NetworkDevice* mDevice; }; } // namespace qs::network diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt index 61f7e66..8a3cac8 100644 --- a/src/network/nm/CMakeLists.txt +++ b/src/network/nm/CMakeLists.txt @@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES dbus_nm_wireless ) +set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES + CLASSNAME DBusNMWiredProxy + NO_NAMESPACE TRUE +) + +qt_add_dbus_interface(NM_DBUS_INTERFACES + org.freedesktop.NetworkManager.Device.Wired.xml + dbus_nm_wired +) + set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES CLASSNAME DBusNMAccessPointProxy NO_NAMESPACE TRUE @@ -67,8 +77,10 @@ qt_add_library(quickshell-network-nm STATIC settings.cpp accesspoint.cpp wireless.cpp + wired.cpp utils.cpp dbus_types.cpp + network.cpp enums.hpp ${NM_DBUS_INTERFACES} ) @@ -79,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus) qs_add_link_dependencies(quickshell-network-nm quickshell-dbus) +qs_add_link_dependencies(quickshell-network-nm quickshell-network) diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp index 63e35ee..d0b2f2e 100644 --- a/src/network/nm/accesspoint.hpp +++ b/src/network/nm/accesspoint.hpp @@ -7,7 +7,7 @@ #include #include "../../dbus/properties.hpp" -#include "../wifi.hpp" +#include "../enums.hpp" #include "dbus_nm_accesspoint.h" #include "enums.hpp" diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index a46ccb2..cda1be4 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -15,15 +15,13 @@ #include "../../core/logcat.hpp" #include "../../dbus/properties.hpp" -#include "../device.hpp" -#include "../enums.hpp" -#include "../network.hpp" -#include "../wifi.hpp" +#include "../qml.hpp" #include "dbus_nm_backend.h" #include "dbus_nm_device.h" #include "dbus_types.hpp" #include "device.hpp" #include "enums.hpp" +#include "wired.hpp" #include "wireless.hpp" namespace qs::network { @@ -133,6 +131,7 @@ void NetworkManager::registerDevice(const QString& path) { switch (type) { case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break; + case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break; default: break; } @@ -147,8 +146,9 @@ void NetworkManager::registerDevice(const QString& path) { QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection); QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection); // clang-format on - - this->registerFrontendDevice(type, dev); + QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() { + emit this->deviceAdded(dev->frontend()); + }); } } else { qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path; @@ -160,67 +160,6 @@ void NetworkManager::registerDevice(const QString& path) { qs::dbus::asyncReadProperty(*temp, "DeviceType", callback); } -void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) { - NetworkDevice* frontendDev = nullptr; - switch (type) { - case NMDeviceType::Wifi: { - auto* frontendWifiDev = new WifiDevice(dev); - auto* wifiDev = qobject_cast(dev); - // Bind WifiDevice-specific properties - auto translateMode = [wifiDev]() { - switch (wifiDev->mode()) { - case NM80211Mode::Unknown: return WifiDeviceMode::Unknown; - case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc; - case NM80211Mode::Infra: return WifiDeviceMode::Station; - case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint; - case NM80211Mode::Mesh: return WifiDeviceMode::Mesh; - } - }; - // clang-format off - frontendWifiDev->bindableMode().setBinding(translateMode); - wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); }); - QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded); - QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved); - // clang-format on - frontendDev = frontendWifiDev; - break; - } - default: return; - } - - // Bind generic NetworkDevice properties - auto translateState = [dev]() { - switch (dev->state()) { - case 0 ... 20: return ConnectionState::Unknown; - case 30: return ConnectionState::Disconnected; - case 40 ... 90: return ConnectionState::Connecting; - case 100: return ConnectionState::Connected; - case 110 ... 120: return ConnectionState::Disconnecting; - } - }; - // clang-format off - frontendDev->bindableName().setBinding([dev]() { return dev->interface(); }); - frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); }); - frontendDev->bindableState().setBinding(translateState); - frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); }); - frontendDev->bindableNmManaged().setBinding([dev]() { return dev->managed(); }); - QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect); - QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect); - QObject::connect(frontendDev, &NetworkDevice::requestSetNmManaged, dev, &NMDevice::setManaged); - // clang-format on - - this->mFrontendDevices.insert(dev->path(), frontendDev); - emit this->deviceAdded(frontendDev); -} - -void NetworkManager::removeFrontendDevice(NMDevice* dev) { - auto* frontendDev = this->mFrontendDevices.take(dev->path()); - if (frontendDev) { - emit this->deviceRemoved(frontendDev); - frontendDev->deleteLater(); - } -} - void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) { this->registerDevice(path.path()); } @@ -235,7 +174,7 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) { this->mDevices.erase(iter); if (dev) { qCDebug(logNetworkManager) << "Device removed:" << path.path(); - this->removeFrontendDevice(dev); + emit this->deviceRemoved(dev->frontend()); delete dev; } } diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index 2825a17..a4f7d17 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -8,7 +8,7 @@ #include #include "../../dbus/properties.hpp" -#include "../network.hpp" +#include "../qml.hpp" #include "dbus_nm_backend.h" #include "dbus_types.hpp" #include "device.hpp" @@ -70,11 +70,8 @@ private: void init(); void registerDevices(); void registerDevice(const QString& path); - void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev); - void removeFrontendDevice(NMDevice* dev); QHash mDevices; - QHash mFrontendDevices; // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged); diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index 1f229c8..0a5f91c 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -14,9 +14,13 @@ #include "../../core/logcat.hpp" #include "../../dbus/properties.hpp" +#include "../device.hpp" +#include "../enums.hpp" #include "active_connection.hpp" #include "dbus_nm_device.h" +#include "dbus_types.hpp" #include "enums.hpp" +#include "network.hpp" #include "settings.hpp" namespace qs::network { @@ -49,6 +53,29 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) { this->deviceProperties.updateAllViaGetAll(); } +void NMDevice::bindFrontend(NetworkDevice* frontend) { + auto translateState = [this]() { + switch (this->state()) { + case 0 ... 20: return ConnectionState::Unknown; + case 30: return ConnectionState::Disconnected; + case 40 ... 90: return ConnectionState::Connecting; + case 100: return ConnectionState::Connected; + case 110 ... 120: return ConnectionState::Disconnecting; + } + }; + // clang-format off + frontend->bindableName().setBinding([this]() { return this->interface(); }); + frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); }); + frontend->bindableState().setBinding(translateState); + frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); }); + frontend->bindableNmManaged().setBinding([this]() { return this->managed(); }); + QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect); + QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect); + QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged); + QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded); + QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved); +} + void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) { auto enumReason = static_cast(reason); auto enumNewState = static_cast(newState); @@ -57,6 +84,22 @@ void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 re this->bStateReason = enumReason; } +void NMDevice::bindNetwork(NMNetwork* net) { + net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); }); + QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect); + QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){ + emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path())); + }); + QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){ + emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject)); + }); + QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) { + if (visible) emit this->networkAdded(net->frontend()); + else emit this->networkRemoved(net->frontend()); + }); + if (net->visible()) emit this->networkAdded(net->frontend()); +} + void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) { const QString stringPath = path.path(); @@ -160,4 +203,9 @@ DBusDataTransform::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 963f574..d6b332b 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -8,9 +8,11 @@ #include #include "../../dbus/properties.hpp" -#include "../enums.hpp" +#include "../device.hpp" #include "active_connection.hpp" #include "dbus_nm_device.h" +#include "enums.hpp" +#include "network.hpp" #include "settings.hpp" namespace qs::dbus { @@ -22,6 +24,13 @@ struct DBusDataTransform { static DBusResult fromWire(Wire wire); }; +template <> +struct DBusDataTransform { + using Wire = quint32; + using Data = qs::network::NMDeviceInterfaceFlags::Enum; + static DBusResult fromWire(Wire wire); +}; + } // namespace qs::dbus namespace qs::network { @@ -44,16 +53,23 @@ public: [[nodiscard]] NMDeviceState::Enum state() const { return this->bState; } [[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; } [[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; } + [[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const { + return this->bInterfaceFlags; + } [[nodiscard]] bool autoconnect() const { return this->bAutoconnect; } [[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; } + [[nodiscard]] virtual NetworkDevice* frontend() = 0; signals: + void loaded(); void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath); void addAndActivateConnection( const NMSettingsMap& settings, const QDBusObjectPath& devPath, - const QDBusObjectPath& apPath + const QDBusObjectPath& specificObjectPath ); + void networkAdded(Network* net); + void networkRemoved(Network* net); void settingsLoaded(NMSettings* settings); void settingsRemoved(NMSettings* settings); void availableSettingsPathsChanged(QList paths); @@ -66,12 +82,17 @@ signals: void stateReasonChanged(NMDeviceStateReason::Enum reason); void lastFailReasonChanged(NMDeviceStateReason::Enum reason); void autoconnectChanged(bool autoconnect); + void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags); public slots: void disconnect(); void setAutoconnect(bool autoconnect); void setManaged(bool managed); +protected: + void bindFrontend(NetworkDevice* frontend); + void bindNetwork(NMNetwork* net); + private slots: void onStateChanged(quint32 newState, quint32 oldState, quint32 reason); void onAvailableSettingsPathsChanged(const QList& paths); @@ -93,6 +114,7 @@ private: Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged); Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList, bAvailableConnections, &NMDevice::availableSettingsPathsChanged); Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface"); @@ -102,6 +124,7 @@ private: QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect"); QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections"); QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection"); + QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags"); // clang-format on DBusNMDeviceProxy* deviceProxy = nullptr; diff --git a/src/network/nm/enums.hpp b/src/network/nm/enums.hpp index 18b1b8b..518c584 100644 --- a/src/network/nm/enums.hpp +++ b/src/network/nm/enums.hpp @@ -183,6 +183,23 @@ public: Q_ENUM(Enum); }; +// Flags for a network interface. +// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags. +class NMDeviceInterfaceFlags: public QObject { + Q_OBJECT; + +public: + enum Enum : quint32 { + None = 0x0, + Up = 0x1, + LowerUp = 0x2, + Promisc = 0x4, + Carrier = 0x10000, + LldpClientEnabled = 0x20000, + }; + Q_ENUM(Enum); +}; + // 802.11 specific device encryption and authentication capabilities. // In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities. class NMWirelessCapabilities: public QObject { diff --git a/src/network/nm/network.cpp b/src/network/nm/network.cpp new file mode 100644 index 0000000..945a438 --- /dev/null +++ b/src/network/nm/network.cpp @@ -0,0 +1,308 @@ +#include "network.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../core/logcat.hpp" +#include "../enums.hpp" +#include "../network.hpp" +#include "../wifi.hpp" +#include "accesspoint.hpp" +#include "active_connection.hpp" +#include "enums.hpp" +#include "settings.hpp" +#include "utils.hpp" + +namespace qs::network { + +namespace { +QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); +} + +NMNetwork::NMNetwork(QObject* parent) + : QObject(parent) + , bKnown(false) + , bReason(NMConnectionStateReason::None) + , bState(NMConnectionState::Deactivated) {} + +void NMNetwork::updateReferenceSettings() { + // If the network has no connections, the reference is nullptr. + if (this->mSettings.isEmpty()) { + this->bReferenceSettings = nullptr; + return; + }; + + // If the network has an active connection, use its settings as the reference. + if (this->mActiveConnection) { + auto* settings = this->mSettings.value(this->mActiveConnection->connection().path()); + if (settings && settings != this->bReferenceSettings) { + this->bReferenceSettings = settings; + } + return; + } + + // Otherwise, choose the settings responsible for the last successful connection. + NMSettings* selectedSettings = nullptr; + quint64 selectedTimestamp = 0; + for (auto* settings: this->mSettings.values()) { + const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong(); + if (!selectedSettings || timestamp > selectedTimestamp) { + selectedSettings = settings; + selectedTimestamp = timestamp; + } + } + + if (this->bReferenceSettings != selectedSettings) { + this->bReferenceSettings = selectedSettings; + } +} + +void NMNetwork::addSettings(NMSettings* settings) { + if (this->mSettings.contains(settings->path())) return; + this->mSettings.insert(settings->path(), settings); + + auto onDestroyed = [this, settings]() { + if (this->mSettings.take(settings->path())) { + emit this->settingsRemoved(settings); + this->updateReferenceSettings(); + if (this->mSettings.isEmpty()) this->bKnown = false; + } + }; + QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed); + this->bKnown = true; + this->updateReferenceSettings(); + emit this->settingsAdded(settings); +}; + +void NMNetwork::addActiveConnection(NMActiveConnection* active) { + if (this->mActiveConnection) return; + this->mActiveConnection = active; + + this->bState.setBinding([active]() { return active->state(); }); + this->bReason.setBinding([active]() { return active->stateReason(); }); + auto onDestroyed = [this, active]() { + if (this->mActiveConnection && this->mActiveConnection == active) { + this->mActiveConnection = nullptr; + this->updateReferenceSettings(); + this->bState = NMConnectionState::Deactivated; + this->bReason = NMConnectionStateReason::None; + } + }; + QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed); + this->updateReferenceSettings(); +}; + +void NMNetwork::forget() { + if (this->mSettings.isEmpty()) return; + for (auto* conn: this->mSettings.values()) { + conn->forget(); + } +} + +void NMNetwork::bindFrontend(Network* frontend) { + auto translateState = [this]() { return this->state() == NMConnectionState::Activated; }; + frontend->bindableConnected().setBinding(translateState); + frontend->bindableKnown().setBinding([this]() { return this->known(); }); + frontend->bindableState().setBinding([this]() { + return static_cast(this->state()); + }); + frontend->bindableStateChanging().setBinding([this]() { + auto s = static_cast(this->state()); + return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting; + }); + + QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() { + if (this->reason() == NMConnectionStateReason::DeviceDisconnected) { + auto deviceReason = this->deviceFailReason(); + if (deviceReason == NMDeviceStateReason::NoSecrets) + emit frontend->connectionFailed(ConnectionFailReason::NoSecrets); + if (deviceReason == NMDeviceStateReason::SupplicantDisconnect) + emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected); + if (deviceReason == NMDeviceStateReason::SupplicantFailed) + emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed); + if (deviceReason == NMDeviceStateReason::SupplicantTimeout) + emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout); + if (deviceReason == NMDeviceStateReason::SsidNotFound) + emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost); + } + }); + + QObject::connect( + frontend, + &Network::requestConnectWithSettings, + this, + [this](NMSettings* settings) { + if (settings) { + emit this->requestActivateConnection(settings->path()); + return; + } + qCInfo( + logNetworkManager + ) << "Failed to connectWithSettings: The provided settings no longer exist."; + } + ); + + // clang-format off + QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget); + QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect); + QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded); + QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved); + // clang-format on +} + +NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent) + : NMNetwork(parent) + , mFrontend(new Network(std::move(name), device, this)) { + // Regiter and bind the frontend Network. + this->bindFrontend(); +} + +void NMGenericNetwork::bindFrontend() { + auto* frontend = this->mFrontend; + this->NMNetwork::bindFrontend(frontend); + QObject::connect(frontend, &Network::requestConnect, this, [this]() { + if (auto* settingsRef = this->referenceSettings()) { + emit this->requestActivateConnection(settingsRef->path()); + return; + } + emit this->requestAddAndActivateConnection(NMSettingsMap(), "/"); + return; + }); +} + +NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent) + : NMNetwork(parent) + , mSsid(ssid) + , bSecurity(WifiSecurityType::Unknown) { + + auto updateSecurity = [this]() { + if (NMSettings* settings = this->bReferenceSettings) { + this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); }); + } else if (NMAccessPoint* ap = this->bReferenceAp) { + this->bSecurity.setBinding([ap]() { return ap->security(); }); + } else { + this->bSecurity = WifiSecurityType::Unknown; + } + return; + }; + + auto checkDisappeared = [this]() { + if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared(); + }; + + QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity); + QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity); + QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared); + QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared); + + // Register and bind the frontend WifiNetwork. + this->mFrontend = new WifiNetwork(ssid, device, this); + this->bindFrontend(); +} + +void NMWirelessNetwork::updateReferenceAp() { + // If the network has no APs, the reference is a nullptr. + if (this->mAccessPoints.isEmpty()) { + this->bReferenceAp = nullptr; + this->bSignalStrength = 0; + return; + } + + // Otherwise, choose the AP with the strongest signal. + NMAccessPoint* selectedAp = nullptr; + for (auto* ap: this->mAccessPoints.values()) { + // Always prefer the active AP. + if (ap->path() == this->bActiveApPath) { + selectedAp = ap; + break; + } + if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) { + selectedAp = ap; + } + } + if (this->bReferenceAp != selectedAp) { + this->bReferenceAp = selectedAp; + this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); }); + } +} + +void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) { + if (this->mAccessPoints.contains(ap->path())) return; + this->mAccessPoints.insert(ap->path(), ap); + auto onDestroyed = [this, ap]() { + if (this->mAccessPoints.take(ap->path())) { + emit this->apRemoved(ap); + this->updateReferenceAp(); + } + }; + // clang-format off + QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp); + QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed); + // clang-format on + this->updateReferenceAp(); +}; + +void NMWirelessNetwork::bindFrontend() { + auto* frontend = this->mFrontend; + this->NMNetwork::bindFrontend(frontend); + + auto translateSignal = [this]() { return this->signalStrength() / 100.0; }; + frontend->bindableSignalStrength().setBinding(translateSignal); + frontend->bindableSecurity().setBinding([this]() { return this->security(); }); + + QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() { + if (auto* settingsRef = this->referenceSettings()) { + emit this->requestActivateConnection(settingsRef->path()); + return; + } + if (auto* apRef = this->referenceAp()) { + emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path()); + return; + } + emit this->requestAddAndActivateConnection(NMSettingsMap(), "/"); + return; + }); + + QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) { + NMSettingsMap settings; + settings["802-11-wireless-security"]["psk"] = psk; + if (const QPointer ref = this->referenceSettings()) { + auto* call = ref->updateSettings(settings); + QObject::connect( + call, + &QDBusPendingCallWatcher::finished, + this, + [this, ref](QDBusPendingCallWatcher* call) { + const QDBusPendingReply<> reply = *call; + if (reply.isError()) { + qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message(); + } else { + if (!ref) { + qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared."; + } else { + emit this->requestActivateConnection(ref->path()); + } + } + delete call; + } + ); + return; + } + if (auto* apRef = this->referenceAp()) { + emit this->requestAddAndActivateConnection(settings, apRef->path()); + return; + } + qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared."; + }); +} + +} // namespace qs::network diff --git a/src/network/nm/network.hpp b/src/network/nm/network.hpp new file mode 100644 index 0000000..b4cc6b3 --- /dev/null +++ b/src/network/nm/network.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include + +#include "../enums.hpp" +#include "../network.hpp" +#include "../wifi.hpp" +#include "accesspoint.hpp" +#include "active_connection.hpp" +#include "enums.hpp" +#include "settings.hpp" + +namespace qs::network { + +// NMNetwork aggregates NMActiveConnections and NMSettings of the same network. +class NMNetwork: public QObject { + Q_OBJECT; + +public: + explicit NMNetwork(QObject* parent = nullptr); + + void addSettings(NMSettings* settings); + void addActiveConnection(NMActiveConnection* active); + void forget(); + void connect(const QString& devPath); + void connectWithSettings(const QString& devPath, NMSettings* settings); + + // clang-format off + [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; } + [[nodiscard]] bool known() const { return this->bKnown; } + [[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; } + QBindable bindableDeviceFailReason() { return &this->bDeviceFailReason; } + [[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; } + [[nodiscard]] QList settings() const { return this->mSettings.values(); } + [[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; } + [[nodiscard]] virtual Network* frontend() = 0; + QBindable bindableVisible() { return &this->bVisible; } + [[nodiscard]] bool visible() const { return this->bVisible; } + // clang-format on + +signals: + void requestDisconnect(); + void requestActivateConnection(const QString& settingsPath); + void + requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject); + + void settingsAdded(NMSettings* settings); + void settingsRemoved(NMSettings* settings); + void stateChanged(NMConnectionState::Enum state); + void knownChanged(bool known); + void reasonChanged(NMConnectionStateReason::Enum reason); + void deviceFailReasonChanged(NMDeviceStateReason::Enum reason); + void referenceSettingsChanged(NMSettings* settings); + void visibilityChanged(bool visible); + +protected: + void bindFrontend(Network* frontend); + QHash mSettings; + Q_OBJECT_BINDABLE_PROPERTY( + NMNetwork, + NMSettings*, + bReferenceSettings, + &NMNetwork::referenceSettingsChanged + ); + +private: + void updateReferenceSettings(); + + NMActiveConnection* mActiveConnection = nullptr; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged); + // clang-format on +}; + +// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network. +// This is useful for devices with one network that don't need to extend the base Network class. +class NMGenericNetwork: public NMNetwork { + Q_OBJECT; + +public: + explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr); + [[nodiscard]] Network* frontend() override { return this->mFrontend; } + +private: + void bindFrontend(); + Network* mFrontend; +}; + +// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality. +class NMWirelessNetwork: public NMNetwork { + Q_OBJECT; + +public: + explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr); + + void addAccessPoint(NMAccessPoint* ap); + + // clang-format off + [[nodiscard]] QString ssid() const { return this->mSsid; } + [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; } + [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; } + [[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; } + [[nodiscard]] QList accessPoints() const { return this->mAccessPoints.values(); } + QBindable bindableActiveApPath() { return &this->bActiveApPath; } + [[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; }; + // clang-format on + +signals: + void disappeared(); + void signalStrengthChanged(quint8 signal); + void securityChanged(WifiSecurityType::Enum security); + void activeApPathChanged(QString path); + void referenceApChanged(NMAccessPoint* ap); + void capabilitiesChanged(NMWirelessCapabilities::Enum caps); + void apRemoved(NMAccessPoint* ap); + +private: + void updateReferenceAp(); + void bindFrontend(); + + WifiNetwork* mFrontend; + QString mSsid; + QHash mAccessPoints; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged); + // clang-format on +}; + +} // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.Wired.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wired.xml new file mode 100644 index 0000000..e8276da --- /dev/null +++ b/src/network/nm/org.freedesktop.NetworkManager.Device.Wired.xml @@ -0,0 +1,4 @@ + + + + diff --git a/src/network/nm/settings.cpp b/src/network/nm/settings.cpp index af36dae..b99b1a3 100644 --- a/src/network/nm/settings.cpp +++ b/src/network/nm/settings.cpp @@ -37,6 +37,7 @@ NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) { qDBusRegisterMetaType>(); qDBusRegisterMetaType(); qDBusRegisterMetaType>(); + qDBusRegisterMetaType>(); this->proxy = new DBusNMConnectionSettingsProxy( "org.freedesktop.NetworkManager", diff --git a/src/network/nm/utils.cpp b/src/network/nm/utils.cpp index afdc796..14b37b0 100644 --- a/src/network/nm/utils.cpp +++ b/src/network/nm/utils.cpp @@ -273,6 +273,7 @@ void manualSettingDemarshall(NMSettingsMap& map) { if (signature == "aa{sv}") return QVariant::fromValue(qdbus_cast>(arg)); if (signature == "a(ayuay)") return QVariant::fromValue(qdbus_cast>(arg)); if (signature == "a(ayuayu)") return QVariant::fromValue(qdbus_cast>(arg)); + if (signature == "a{ss}") return QVariant::fromValue(qdbus_cast>(arg)); return value; }; @@ -291,7 +292,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar if (s == "802-1x.ca-cert" || s == "802-1x.client-cert" || s == "802-1x.private-key" || s == "802-1x.password-raw" || s == "802-1x.phase2-ca-cert" || s == "802-1x.phase2-client-cert" || s == "802-1x.phase2-private-key" - || s == "802-11-wireless.ssid") + || s == "802-11-wireless.ssid" || s == "802-3-ethernet.cloned-mac-address" + || s == "802-3-ethernet.mac-address") { if (value.typeId() == QMetaType::QString) { return value.toString().toUtf8(); @@ -423,7 +425,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar // QVariantList -> QStringList if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search" - || s == "802-11-wireless.seen-bssids") + || s == "802-11-wireless.seen-bssids" || s == "802-3-ethernet.mac-address-blacklist" + || s == "802-3-ethernet.mac-address-denylist" || s == "802-3-ethernet.s390-subchannels") { if (value.typeId() == QMetaType::QVariantList) { QStringList stringList; @@ -435,6 +438,18 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar return QVariant(); } + // QVariantMap -> QMap + if (s == "802-3-ethernet.s390-options") { + if (value.canConvert()) { + QMap r; + for (const auto& [key, val]: value.toMap().asKeyValueRange()) { + r.insert(key, val.toString()); + } + return QVariant::fromValue(r); + } + return QVariant(); + } + // double (whole number) -> qint32 if (value.typeId() == QMetaType::Double) { auto num = value.toDouble(); @@ -498,6 +513,15 @@ QVariant settingTypeToQml(const QVariant& value) { return out; } + // QMap -> QVariantMap + if (value.userType() == qMetaTypeId>()) { + QVariantMap out; + for (const auto& [key, val]: value.value>().asKeyValueRange()) { + out.insert(key, val); + } + return out; + } + return value; } diff --git a/src/network/nm/wired.cpp b/src/network/nm/wired.cpp new file mode 100644 index 0000000..e2f172b --- /dev/null +++ b/src/network/nm/wired.cpp @@ -0,0 +1,101 @@ +#include "wired.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "../../core/logcat.hpp" +#include "../../dbus/properties.hpp" +#include "../wired.hpp" +#include "active_connection.hpp" +#include "dbus_nm_wired.h" +#include "device.hpp" +#include "enums.hpp" +#include "network.hpp" +#include "settings.hpp" + +namespace qs::network { +using namespace qs::dbus; + +namespace { +QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); +} + +NMWiredDevice::NMWiredDevice(const QString& path, QObject* parent): NMDevice(path, parent) { + this->wiredProxy = new DBusNMWiredProxy( + "org.freedesktop.NetworkManager", + path, + QDBusConnection::systemBus(), + this + ); + + if (!this->wiredProxy->isValid()) { + qCWarning(logNetworkManager) << "Cannot create DBus interface for wired device at" << path; + return; + } + + // Wait to create the NMGenericNetwork/Network + // until the dbus properties load because the network requires the interface name. + QObject::connect( + &this->wiredProperties, + &DBusPropertyGroup::getAllFinished, + this, + &NMWiredDevice::initWired, + Qt::SingleShotConnection + ); + + this->wiredProperties.setInterface(this->wiredProxy); + this->wiredProperties.updateAllViaGetAll(); + + // Register and bind the frontend WiredDevice. + this->mFrontend = new WiredDevice(this); + this->bindFrontend(); +}; + +void NMWiredDevice::initWired() { + // Register the NMGenericNetwork and bind the frontend Network. + // For wired networking, there is only one Network and it should exist for the lifetime of the device. + auto* net = new NMGenericNetwork(QString(), this->frontend(), this); + net->frontend()->bindableName().setBinding([this]() { return this->interface(); }); + + auto visible = [this]() { + return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0; + }; + net->bindableVisible().setBinding(visible); + this->NMDevice::bindNetwork(net); + this->mNetwork = net; + + // clang-format off + QObject::connect(this, &NMWiredDevice::settingsLoaded, this, &NMWiredDevice::onSettingsLoaded); + QObject::connect(this, &NMWiredDevice::activeConnectionLoaded, this, &NMWiredDevice::onActiveConnectionLoaded); + // clang-format on + + emit this->loaded(); +} + +void NMWiredDevice::bindFrontend() { + auto* frontend = this->mFrontend; + this->NMDevice::bindFrontend(frontend); + frontend->bindableLinkSpeed().setBinding([this]() { return this->bSpeed.value(); }); + frontend->bindableHasLink().setBinding([this]() { + return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0; + }); +} + +void NMWiredDevice::onSettingsLoaded(NMSettings* settings) { + this->mNetwork->addSettings(settings); +} + +void NMWiredDevice::onActiveConnectionLoaded(NMActiveConnection* active) { + this->mNetwork->addActiveConnection(active); +} + +bool NMWiredDevice::isValid() const { + return this->NMDevice::isValid() && (this->wiredProxy && this->wiredProxy->isValid()); +} + +} // namespace qs::network diff --git a/src/network/nm/wired.hpp b/src/network/nm/wired.hpp new file mode 100644 index 0000000..4f561af --- /dev/null +++ b/src/network/nm/wired.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "../wired.hpp" +#include "active_connection.hpp" +#include "dbus_nm_wired.h" +#include "device.hpp" +#include "network.hpp" +#include "settings.hpp" + +namespace qs::network { + +// Proxy of a /org/freedesktop/NetworkManager/Device/* object. +// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wired interface +// Owns the lifetime of a NMGenericNetwork, and a frontend WiredDevice. +class NMWiredDevice: public NMDevice { + Q_OBJECT; + +public: + explicit NMWiredDevice(const QString& path, QObject* parent = nullptr); + + [[nodiscard]] bool isValid() const override; + [[nodiscard]] WiredDevice* frontend() override { return this->mFrontend; }; + +signals: + void speedChanged(quint32 speed); + +private slots: + void onSettingsLoaded(NMSettings* settings); + void onActiveConnectionLoaded(NMActiveConnection* active); + +private: + void initWired(); + void bindFrontend(); + + WiredDevice* mFrontend = nullptr; + NMGenericNetwork* mNetwork = nullptr; + + Q_OBJECT_BINDABLE_PROPERTY(NMWiredDevice, quint32, bSpeed, &NMWiredDevice::speedChanged); + + QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wiredProperties); + QS_DBUS_PROPERTY_BINDING(NMWiredDevice, pSpeed, bSpeed, wiredProperties, "Speed"); + + DBusNMWiredProxy* wiredProxy = nullptr; +}; + +}; // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp index 5f55bed..58a89b5 100644 --- a/src/network/nm/wireless.cpp +++ b/src/network/nm/wireless.cpp @@ -1,5 +1,4 @@ #include "wireless.hpp" -#include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -28,6 +26,7 @@ #include "dbus_types.hpp" #include "device.hpp" #include "enums.hpp" +#include "network.hpp" #include "settings.hpp" #include "utils.hpp" @@ -38,143 +37,6 @@ namespace { QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); } -NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent) - : QObject(parent) - , mSsid(std::move(ssid)) - , bKnown(false) - , bSecurity(WifiSecurityType::Unknown) - , bReason(NMConnectionStateReason::None) - , bState(NMConnectionState::Deactivated) {} - -void NMWirelessNetwork::updateReferenceSettings() { - // If the network has no connections, the reference is nullptr. - if (this->mSettings.isEmpty()) { - this->mReferenceSettings = nullptr; - this->bSecurity = WifiSecurityType::Unknown; - if (this->mReferenceAp) { - this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); }); - } - return; - }; - - // If the network has an active connection, use its settings as the reference. - if (this->mActiveConnection) { - auto* settings = this->mSettings.value(this->mActiveConnection->connection().path()); - if (settings && settings != this->mReferenceSettings) { - this->mReferenceSettings = settings; - this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); }); - } - return; - } - - // Otherwise, choose the settings responsible for the last successful connection. - NMSettings* selectedSettings = nullptr; - quint64 selectedTimestamp = 0; - for (auto* settings: this->mSettings.values()) { - const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong(); - if (!selectedSettings || timestamp > selectedTimestamp) { - selectedSettings = settings; - selectedTimestamp = timestamp; - } - } - - if (this->mReferenceSettings != selectedSettings) { - this->mReferenceSettings = selectedSettings; - this->bSecurity.setBinding([selectedSettings]() { - return securityFromSettingsMap(selectedSettings->map()); - }); - } -} - -void NMWirelessNetwork::updateReferenceAp() { - // If the network has no APs, the reference is a nullptr. - if (this->mAccessPoints.isEmpty()) { - this->mReferenceAp = nullptr; - this->bSignalStrength = 0; - return; - } - - // Otherwise, choose the AP with the strongest signal. - NMAccessPoint* selectedAp = nullptr; - for (auto* ap: this->mAccessPoints.values()) { - // Always prefer the active AP. - if (ap->path() == this->bActiveApPath) { - selectedAp = ap; - break; - } - if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) { - selectedAp = ap; - } - } - if (this->mReferenceAp != selectedAp) { - this->mReferenceAp = selectedAp; - this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); }); - // Reference AP is used for security when there's no connection settings. - if (!this->mReferenceSettings) { - this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); }); - } - } -} - -void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) { - if (this->mAccessPoints.contains(ap->path())) return; - this->mAccessPoints.insert(ap->path(), ap); - auto onDestroyed = [this, ap]() { - if (this->mAccessPoints.take(ap->path())) { - this->updateReferenceAp(); - if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared(); - } - }; - // clang-format off - QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp); - QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed); - // clang-format on - this->updateReferenceAp(); -}; - -void NMWirelessNetwork::addSettings(NMSettings* settings) { - if (this->mSettings.contains(settings->path())) return; - this->mSettings.insert(settings->path(), settings); - - auto onDestroyed = [this, settings]() { - if (this->mSettings.take(settings->path())) { - emit this->settingsRemoved(settings); - this->updateReferenceSettings(); - if (this->mSettings.isEmpty()) this->bKnown = false; - if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared(); - } - }; - QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed); - this->bKnown = true; - this->updateReferenceSettings(); - emit this->settingsAdded(settings); -}; - -void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) { - if (this->mActiveConnection) return; - this->mActiveConnection = active; - - this->bState.setBinding([active]() { return active->state(); }); - this->bReason.setBinding([active]() { return active->stateReason(); }); - auto onDestroyed = [this, active]() { - if (this->mActiveConnection && this->mActiveConnection == active) { - this->mActiveConnection = nullptr; - this->updateReferenceSettings(); - this->bState = NMConnectionState::Deactivated; - this->bReason = NMConnectionStateReason::None; - } - }; - QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed); - this->updateReferenceSettings(); -}; - -void NMWirelessNetwork::forget() { - if (this->mSettings.isEmpty()) return; - for (auto* conn: this->mSettings.values()) { - conn->forget(); - } -} - NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent) : NMDevice(path, parent) , mScanTimer(this) { @@ -203,6 +65,10 @@ NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent) this->wirelessProperties.setInterface(this->wirelessProxy); this->wirelessProperties.updateAllViaGetAll(); + + // Register and bind the frontend WifiDevice. + this->mFrontend = new WifiDevice(this); + this->bindFrontend(); } void NMWirelessDevice::initWireless() { @@ -214,7 +80,9 @@ void NMWirelessDevice::initWireless() { QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded); QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged); // clang-format on + this->registerAccessPoints(); + emit this->loaded(); } void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) { @@ -364,176 +232,45 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) { } NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) { - auto* net = new NMWirelessNetwork(ssid, this); + auto* net = new NMWirelessNetwork(ssid, this->frontend(), this); + this->NMDevice::bindNetwork(net); auto visible = [this, net]() { return this->bScanning || net->state() == NMConnectionState::Activated || net->known(); }; - net->bindableVisible().setBinding(visible); net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); }); - net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); }); QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork); - qCDebug(logNetworkManager) << "Registered network for SSID" << ssid; this->mNetworks.insert(ssid, net); - this->registerFrontendNetwork(net); + qCDebug(logNetworkManager) << "Registered network for SSID" << ssid; return net; } -void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) { - auto ssid = net->ssid(); - auto* frontendNet = new WifiNetwork(ssid, net); - - // Bind WifiNetwork to NMWirelessNetwork - auto translateSignal = [net]() { return net->signalStrength() / 100.0; }; - auto translateState = [net]() { return net->state() == NMConnectionState::Activated; }; - frontendNet->bindableSignalStrength().setBinding(translateSignal); - frontendNet->bindableConnected().setBinding(translateState); - frontendNet->bindableKnown().setBinding([net]() { return net->known(); }); - frontendNet->bindableSecurity().setBinding([net]() { return net->security(); }); - frontendNet->bindableState().setBinding([net]() { - return static_cast(net->state()); - }); - - QObject::connect(net, &NMWirelessNetwork::reasonChanged, this, [net, frontendNet]() { - if (net->reason() == NMConnectionStateReason::DeviceDisconnected) { - auto deviceReason = net->deviceFailReason(); - if (deviceReason == NMDeviceStateReason::NoSecrets) - emit frontendNet->connectionFailed(ConnectionFailReason::NoSecrets); - if (deviceReason == NMDeviceStateReason::SupplicantDisconnect) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientDisconnected); - if (deviceReason == NMDeviceStateReason::SupplicantFailed) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientFailed); - if (deviceReason == NMDeviceStateReason::SupplicantTimeout) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiAuthTimeout); - if (deviceReason == NMDeviceStateReason::SsidNotFound) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiNetworkLost); - } - }); - - QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() { - if (net->referenceSettings()) { - emit this->activateConnection( - QDBusObjectPath(net->referenceSettings()->path()), - QDBusObjectPath(this->path()) - ); - return; - } - if (net->referenceAp()) { - emit this->addAndActivateConnection( - NMSettingsMap(), - QDBusObjectPath(this->path()), - QDBusObjectPath(net->referenceAp()->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connect to" - << this->path() + ": The network disappeared."; - }); - - QObject::connect( - frontendNet, - &WifiNetwork::requestConnectWithPsk, - this, - [this, net](const QString& psk) { - NMSettingsMap settings; - settings["802-11-wireless-security"]["psk"] = psk; - if (const QPointer ref = net->referenceSettings()) { - auto* call = ref->updateSettings(settings); - QObject::connect( - call, - &QDBusPendingCallWatcher::finished, - this, - [this, ref](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCInfo(logNetworkManager) - << "Failed to write PSK for" << this->path() + ":" << reply.error().message(); - } else { - if (!ref) { - qCInfo(logNetworkManager) << "Failed to connectWithPsk to" - << this->path() + ": The settings disappeared."; - } else { - emit this->activateConnection( - QDBusObjectPath(ref->path()), - QDBusObjectPath(this->path()) - ); - } - } - delete call; - } - ); - return; - } - if (net->referenceAp()) { - emit this->addAndActivateConnection( - settings, - QDBusObjectPath(this->path()), - QDBusObjectPath(net->referenceAp()->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connectWithPsk to" - << this->path() + ": The network disappeared."; - } - ); - - QObject::connect( - frontendNet, - &WifiNetwork::requestConnectWithSettings, - this, - [this](NMSettings* settings) { - if (settings) { - emit this->activateConnection( - QDBusObjectPath(settings->path()), - QDBusObjectPath(this->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connectWithSettings to" - << this->path() + ": The provided settings no longer exist."; - } - ); - - QObject::connect( - net, - &NMWirelessNetwork::visibilityChanged, - this, - [this, frontendNet](bool visible) { - if (visible) this->networkAdded(frontendNet); - else this->networkRemoved(frontendNet); - } - ); - - // clang-format off - QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect); - QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget); - QObject::connect(net, &NMWirelessNetwork::settingsAdded, frontendNet, &WifiNetwork::settingsAdded); - QObject::connect(net, &NMWirelessNetwork::settingsRemoved, frontendNet, &WifiNetwork::settingsRemoved); - // clang-format on - - this->mFrontendNetworks.insert(ssid, frontendNet); - if (net->visible()) emit this->networkAdded(frontendNet); -} - -void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) { - auto* frontendNet = this->mFrontendNetworks.take(net->ssid()); - if (frontendNet) { - if (net->visible()) emit this->networkRemoved(frontendNet); - frontendNet->deleteLater(); - } -} - void NMWirelessDevice::removeNetwork() { auto* net = qobject_cast(this->sender()); if (this->mNetworks.take(net->ssid())) { - this->removeFrontendNetwork(net); + if (net->visible()) emit this->networkRemoved(net->frontend()); delete net; }; } +void NMWirelessDevice::bindFrontend() { + auto* frontend = this->mFrontend; + this->NMDevice::bindFrontend(frontend); + auto translateMode = [this]() { + switch (this->mode()) { + case NM80211Mode::Unknown: return WifiDeviceMode::Unknown; + case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc; + case NM80211Mode::Infra: return WifiDeviceMode::Station; + case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint; + case NM80211Mode::Mesh: return WifiDeviceMode::Mesh; + } + }; + frontend->bindableMode().setBinding(translateMode); + this->bindableScanning().setBinding([frontend]() { return frontend->scannerEnabled(); }); +} + bool NMWirelessDevice::isValid() const { return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid()); } diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp index 94ce754..b0c55f5 100644 --- a/src/network/nm/wireless.hpp +++ b/src/network/nm/wireless.hpp @@ -13,6 +13,7 @@ #include "dbus_nm_wireless.h" #include "device.hpp" #include "enums.hpp" +#include "network.hpp" #include "settings.hpp" namespace qs::dbus { @@ -33,76 +34,9 @@ struct DBusDataTransform { } // namespace qs::dbus namespace qs::network { -// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMSettings objects. -class NMWirelessNetwork: public QObject { - Q_OBJECT; - -public: - explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr); - - void addAccessPoint(NMAccessPoint* ap); - void addSettings(NMSettings* settings); - void addActiveConnection(NMActiveConnection* active); - void forget(); - - // clang-format off - [[nodiscard]] QString ssid() const { return this->mSsid; } - [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; } - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; } - [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; } - [[nodiscard]] bool known() const { return this->bKnown; } - [[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; } - QBindable bindableDeviceFailReason() { return &this->bDeviceFailReason; } - [[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; } - [[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; } - [[nodiscard]] QList accessPoints() const { return this->mAccessPoints.values(); } - [[nodiscard]] QList settings() const { return this->mSettings.values(); } - [[nodiscard]] NMSettings* referenceSettings() const { return this->mReferenceSettings; } - QBindable bindableActiveApPath() { return &this->bActiveApPath; } - QBindable bindableVisible() { return &this->bVisible; } - bool visible() const { return this->bVisible; } - // clang-format on - -signals: - void disappeared(); - void settingsAdded(NMSettings* settings); - void settingsRemoved(NMSettings* settings); - void visibilityChanged(bool visible); - void signalStrengthChanged(quint8 signal); - void stateChanged(NMConnectionState::Enum state); - void knownChanged(bool known); - void securityChanged(WifiSecurityType::Enum security); - void reasonChanged(NMConnectionStateReason::Enum reason); - void deviceFailReasonChanged(NMDeviceStateReason::Enum reason); - void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void activeApPathChanged(QString path); - -private: - void updateReferenceAp(); - void updateReferenceSettings(); - - QString mSsid; - QHash mAccessPoints; - QHash mSettings; - NMAccessPoint* mReferenceAp = nullptr; - NMSettings* mReferenceSettings = nullptr; - NMActiveConnection* mActiveConnection = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMWirelessNetwork::deviceFailReasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged); - // clang-format on -}; - // Proxy of a /org/freedesktop/NetworkManager/Device/* object. // Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface -// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s). +// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), and a frontend WifiDevice. class NMWirelessDevice: public NMDevice { Q_OBJECT; @@ -114,12 +48,11 @@ public: [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; } [[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; } [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; } + [[nodiscard]] WifiDevice* frontend() override { return this->mFrontend; }; signals: void accessPointLoaded(NMAccessPoint* ap); void accessPointRemoved(NMAccessPoint* ap); - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); void lastScanChanged(QDateTime lastScan); void scanningChanged(bool scanning); void capabilitiesChanged(NMWirelessCapabilities::Enum caps); @@ -137,17 +70,16 @@ private slots: private: void registerAccessPoint(const QString& path); - void registerFrontendNetwork(NMWirelessNetwork* net); - void removeFrontendNetwork(NMWirelessNetwork* net); void removeNetwork(); bool checkVisibility(WifiNetwork* net); void registerAccessPoints(); void initWireless(); + void bindFrontend(); NMWirelessNetwork* registerNetwork(const QString& ssid); + WifiDevice* mFrontend; QHash mAccessPoints; QHash mNetworks; - QHash mFrontendNetworks; QDateTime mLastScanRequest; QTimer mScanTimer; diff --git a/src/network/qml.cpp b/src/network/qml.cpp new file mode 100644 index 0000000..3156981 --- /dev/null +++ b/src/network/qml.cpp @@ -0,0 +1,82 @@ +#include "qml.hpp" + +#include +#include +#include +#include +#include +#include + +#include "../core/logcat.hpp" +#include "device.hpp" +#include "enums.hpp" +#include "nm/backend.hpp" + +namespace qs::network { + +namespace { +QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); +} // namespace + +Networking::Networking(QObject* parent): QObject(parent) { + // Try to create the NetworkManager backend and bind to it. + auto* nm = new NetworkManager(this); + if (nm->isAvailable()) { + // clang-format off + QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded); + QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved); + QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled); + QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled); + QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity); + this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); }); + this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); }); + this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); }); + this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); }); + this->bindableConnectivity().setBinding([nm]() { return static_cast(nm->connectivity()); }); + // clang-format on + + this->mBackend = nm; + this->mBackendType = NetworkBackendType::NetworkManager; + return; + } else { + delete nm; + } + qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; +} + +Networking* Networking::instance() { + static Networking* instance = new Networking(); // NOLINT + return instance; +} + +void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); } +void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); } + +void Networking::checkConnectivity() { + if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return; + emit this->requestCheckConnectivity(); +} + +void Networking::setWifiEnabled(bool enabled) { + if (this->bWifiEnabled == enabled) return; + emit this->requestSetWifiEnabled(enabled); +} + +void Networking::setConnectivityCheckEnabled(bool enabled) { + if (this->bConnectivityCheckEnabled == enabled) return; + emit this->requestSetConnectivityCheckEnabled(enabled); +} + +NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) { + // clang-format off + QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged); + QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged); + QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged); + QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged); + QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged); + // clang-format on +} + +void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); } + +} // namespace qs::network diff --git a/src/network/qml.hpp b/src/network/qml.hpp new file mode 100644 index 0000000..ea5bb88 --- /dev/null +++ b/src/network/qml.hpp @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "../core/doc.hpp" +#include "../core/model.hpp" +#include "device.hpp" +#include "enums.hpp" + +namespace qs::network { + +class NetworkBackend: public QObject { + Q_OBJECT; + +public: + [[nodiscard]] virtual bool isAvailable() const = 0; + +protected: + explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; +}; + +class Networking: public QObject { + Q_OBJECT; + +public: + static Networking* instance(); + + void checkConnectivity(); + + [[nodiscard]] ObjectModel* devices() { return &this->mDevices; } + [[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; } + QBindable bindableWifiEnabled() { return &this->bWifiEnabled; } + [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; } + void setWifiEnabled(bool enabled); + QBindable bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; } + QBindable bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; } + QBindable bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; } + [[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; } + void setConnectivityCheckEnabled(bool enabled); + QBindable bindableConnectivity() { return &this->bConnectivity; } + +signals: + void requestSetWifiEnabled(bool enabled); + void requestSetConnectivityCheckEnabled(bool enabled); + void requestCheckConnectivity(); + + void wifiEnabledChanged(); + void wifiHardwareEnabledChanged(); + void canCheckConnectivityChanged(); + void connectivityCheckEnabledChanged(); + void connectivityChanged(); + +private slots: + void deviceAdded(NetworkDevice* dev); + void deviceRemoved(NetworkDevice* dev); + +private: + explicit Networking(QObject* parent = nullptr); + + ObjectModel mDevices {this}; + NetworkBackend* mBackend = nullptr; + NetworkBackendType::Enum mBackendType = NetworkBackendType::None; + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged); + Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged); + Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged); + Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged); + Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged); + // clang-format on +}; + +///! The Network service. +/// An interface to a network backend (currently only NetworkManager), +/// which can be used to view, configure, and connect to various networks. +class NetworkingQml: public QObject { + Q_OBJECT; + QML_NAMED_ELEMENT(Networking); + QML_SINGLETON; + // clang-format off + /// A list of all network devices. Networks are exposed through their respective devices. + QSDOC_TYPE_OVERRIDE(ObjectModel*); + Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); + /// The backend being used to power the Network service. + Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT); + /// Switch for the rfkill software block of all wireless devices. + Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged); + /// State of the rfkill hardware block of all wireless devices. + Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled); + /// True if the @@backend supports connectivity checks. + Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity); + /// True if connectivity checking is enabled. + Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged); + /// The result of the last connectivity check. + /// + /// Connectivity checks may require additional configuration depending on your distro. + /// + /// > [!NOTE] This property can be used to determine if network access is restricted + /// > or gated behind a captive portal. + /// > + /// > If checking for captive portals, @@checkConnectivity() should be called after + /// > the portal is dismissed to update this property. + Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity); + // clang-format on + +public: + explicit NetworkingQml(QObject* parent = nullptr); + + /// Re-check the network connectivity state immediately. + /// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal. + Q_INVOKABLE static void checkConnectivity(); + + [[nodiscard]] static ObjectModel* devices() { + return Networking::instance()->devices(); + } + [[nodiscard]] static NetworkBackendType::Enum backend() { + return Networking::instance()->backend(); + } + [[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); } + static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); } + [[nodiscard]] static QBindable bindableWifiHardwareEnabled() { + return Networking::instance()->bindableWifiHardwareEnabled(); + } + [[nodiscard]] static QBindable bindableWifiEnabled() { + return Networking::instance()->bindableWifiEnabled(); + } + [[nodiscard]] static QBindable bindableCanCheckConnectivity() { + return Networking::instance()->bindableCanCheckConnectivity(); + } + [[nodiscard]] static bool connectivityCheckEnabled() { + return Networking::instance()->connectivityCheckEnabled(); + } + static void setConnectivityCheckEnabled(bool enabled) { + Networking::instance()->setConnectivityCheckEnabled(enabled); + } + [[nodiscard]] static QBindable bindableConnectivity() { + return Networking::instance()->bindableConnectivity(); + } + +signals: + void wifiEnabledChanged(); + void wifiHardwareEnabledChanged(); + void canCheckConnectivityChanged(); + void connectivityCheckEnabledChanged(); + void connectivityChanged(); +}; + +} // namespace qs::network diff --git a/src/network/test/manual/network.qml b/src/network/test/manual/network.qml index eadc159..539cbf8 100644 --- a/src/network/test/manual/network.qml +++ b/src/network/test/manual/network.qml @@ -73,6 +73,241 @@ Scope { } } + Component { + id: deviceDelegate + WrapperRectangle { + width: parent.width + color: "transparent" + border.color: palette.button + border.width: 1 + margin: 5 + + ColumnLayout { + RowLayout { + Label { + text: modelData.name + font.bold: true + } + Label { + text: modelData.address + } + Label { + text: `(Type: ${DeviceType.toString(modelData.type)})` + } + CheckBox { + text: `Managed` + checked: modelData.nmManaged + onClicked: modelData.nmManaged = !modelData.nmManaged + } + } + RowLayout { + Label { + text: ConnectionState.toString(modelData.state) + color: modelData.connected ? palette.link : palette.placeholderText + } + Button { + visible: modelData.state == ConnectionState.Connected + text: "Disconnect" + onClicked: modelData.disconnect() + } + CheckBox { + text: "Autoconnect" + checked: modelData.autoconnect + onClicked: modelData.autoconnect = !modelData.autoconnect + } + RowLayout { + visible: modelData.type === DeviceType.Wired + CheckBox { + text: "Link connected" + checked: modelData.hasLink + enabled: false + } + Label { + text: `Link max speed: ${modelData.linkSpeed} Mbps` + } + } + RowLayout { + visible: modelData.type === DeviceType.Wifi + Label { + text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}` + visible: modelData.type == DeviceType.Wifi + } + CheckBox { + text: "Scanner" + checked: modelData.scannerEnabled + onClicked: modelData.scannerEnabled = !modelData.scannerEnabled + visible: modelData.type === DeviceType.Wifi + } + } + } + + Repeater { + model: ScriptModel { + values: [...modelData.networks.values].sort((a, b) => { + if (a.connected !== b.connected) { + return b.connected - a.connected; + } + if (modelData.device?.type === DeviceType.Wifi) { + return b.signalStrength - a.signalStrength; + } + }) + } + + WrapperRectangle { + id: ethernetNetwork + property var chosenSettings: { + const settings = modelData.nmSettings; + if (!settings || settings.length === 0) { + return null; + } + if (settings.length === 1) { + return settings[0]; + } + return settings[settingsComboBox.currentIndex]; + } + + Connections { + target: modelData + function onConnectionFailed(reason) { + failLoader.sourceComponent = failComponent; + failLoader.item.failReason = reason; + } + function onStateChanged() { + if (modelData.state == ConnectionState.Connecting) { + failLoader.sourceComponent = null; + } + } + } + + Component { + id: failComponent + RowLayout { + property var failReason + Label { + text: ConnectionFailReason.toString(failReason) + } + RowLayout { + TextField { + id: pskField + placeholderText: "PSK" + } + Button { + text: "Set" + visible: pskField.visible + onClicked: { + modelData.connectWithPsk(pskField.text); + failLoader.sourceComponent = null; + } + } + visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae + } + Button { + text: "Close" + onClicked: failLoader.sourceComponent = null + } + } + } + + Layout.fillWidth: true + color: modelData.connected ? palette.highlight : palette.button + border.color: palette.mid + border.width: 1 + margin: 5 + + RowLayout { + ColumnLayout { + Layout.fillWidth: true + RowLayout { + Label { + text: modelData.name + font.bold: true + } + Label { + text: modelData.known ? "Known" : "" + color: palette.placeholderText + } + } + RowLayout { + visible: modelData.device?.type === DeviceType.Wifi + Label { + text: `Security: ${WifiSecurityType.toString(modelData.security)}` + color: palette.placeholderText + } + Label { + text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%` + color: palette.placeholderText + } + } + } + ColumnLayout { + Layout.alignment: Qt.AlignRight + RowLayout { + Layout.alignment: Qt.AlignRight + BusyIndicator { + implicitHeight: 30 + implicitWidth: 30 + running: modelData.stateChanging + visible: modelData.stateChanging + } + Label { + text: ConnectionState.toString(modelData.state) + color: modelData.connected ? palette.link : palette.placeholderText + } + RowLayout { + Label { + text: "Choose settings:" + } + ComboBox { + id: settingsComboBox + model: modelData.nmSettings.map(s => s?.read()?.connection?.id) + currentIndex: 0 + } + visible: modelData.nmSettings.length > 1 + } + Button { + text: "Connect" + onClicked: { + if (ethernetNetwork.chosenSettings) + modelData.connectWithSettings(ethernetNetwork.chosenSettings); + else + modelData.connect(); + } + visible: !modelData.connected + } + Button { + text: "Disconnect" + onClicked: modelData.disconnect() + visible: modelData.connected + } + Button { + text: "Forget" + onClicked: modelData.forget() + visible: modelData.known + } + Button { + text: "Edit" + visible: modelData.known + onClicked: { + if (ethernetNetwork.chosenSettings) + editorComponent.createObject(null, { + nmSettings: chosenSettings + }); + } + } + } + Loader { + id: failLoader + Layout.alignment: Qt.AlignRight + visible: sourceComponent !== null + } + } + } + } + } + } + } + } + FloatingWindow { color: contentItem.palette.window @@ -139,219 +374,11 @@ Scope { Layout.fillWidth: true Layout.fillHeight: true model: Networking.devices - - delegate: WrapperRectangle { - width: parent.width - color: "transparent" - border.color: palette.button - border.width: 1 - margin: 5 - - ColumnLayout { - RowLayout { - Label { - text: modelData.name - font.bold: true - } - Label { - text: modelData.address - } - Label { - text: `(Type: ${DeviceType.toString(modelData.type)})` - } - CheckBox { - text: `Managed` - checked: modelData.nmManaged - onClicked: modelData.nmManaged = !modelData.nmManaged - } - } - RowLayout { - Label { - text: ConnectionState.toString(modelData.state) - color: modelData.connected ? palette.link : palette.placeholderText - } - Button { - visible: modelData.state == ConnectionState.Connected - text: "Disconnect" - onClicked: modelData.disconnect() - } - CheckBox { - text: "Autoconnect" - checked: modelData.autoconnect - onClicked: modelData.autoconnect = !modelData.autoconnect - } - Label { - text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}` - visible: modelData.type == DeviceType.Wifi - } - CheckBox { - text: "Scanner" - checked: modelData.scannerEnabled - onClicked: modelData.scannerEnabled = !modelData.scannerEnabled - visible: modelData.type === DeviceType.Wifi - } - } - - Repeater { - Layout.fillWidth: true - model: ScriptModel { - values: [...modelData.networks.values].sort((a, b) => { - if (a.connected !== b.connected) { - return b.connected - a.connected; - } - return b.signalStrength - a.signalStrength; - }) - } - - WrapperRectangle { - property var chosenSettings: { - const settings = modelData.nmSettings; - if (!settings || settings.length === 0) { - return null; - } - if (settings.length === 1) { - return settings[0]; - } - return settings[settingsComboBox.currentIndex]; - } - - Connections { - target: modelData - function onConnectionFailed(reason) { - failLoader.sourceComponent = failComponent; - failLoader.item.failReason = reason; - } - function onStateChanged() { - if (modelData.state == ConnectionState.Connecting) { - failLoader.sourceComponent = null; - } - } - } - - Component { - id: failComponent - RowLayout { - property var failReason - Label { - text: ConnectionFailReason.toString(failReason) - } - RowLayout { - TextField { - id: pskField - placeholderText: "PSK" - } - Button { - text: "Set" - visible: pskField.visible - onClicked: { - modelData.connectWithPsk(pskField.text); - failLoader.sourceComponent = null; - } - } - visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae - } - Button { - text: "Close" - onClicked: failLoader.sourceComponent = null - } - } - } - - Layout.fillWidth: true - color: modelData.connected ? palette.highlight : palette.button - border.color: palette.mid - border.width: 1 - margin: 5 - - RowLayout { - ColumnLayout { - Layout.fillWidth: true - RowLayout { - Label { - text: modelData.name - font.bold: true - } - Label { - text: modelData.known ? "Known" : "" - color: palette.placeholderText - } - } - RowLayout { - Label { - text: `Security: ${WifiSecurityType.toString(modelData.security)}` - color: palette.placeholderText - } - Label { - text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%` - color: palette.placeholderText - } - } - } - ColumnLayout { - Layout.alignment: Qt.AlignRight - RowLayout { - Layout.alignment: Qt.AlignRight - BusyIndicator { - implicitHeight: 30 - implicitWidth: 30 - running: modelData.stateChanging - visible: modelData.stateChanging - } - Label { - text: ConnectionState.toString(modelData.state) - color: modelData.connected ? palette.link : palette.placeholderText - } - RowLayout { - Label { - text: "Choose settings:" - } - ComboBox { - id: settingsComboBox - model: modelData.nmSettings.map(s => s?.read()?.connection?.id) - currentIndex: 0 - } - visible: modelData.nmSettings.length > 1 - } - Button { - text: "Connect" - onClicked: { - if (chosenSettings) - modelData.connectWithSettings(chosenSettings); - else - modelData.connect(); - } - visible: !modelData.connected - } - Button { - text: "Disconnect" - onClicked: modelData.disconnect() - visible: modelData.connected - } - Button { - text: "Forget" - onClicked: modelData.forget() - visible: modelData.known - } - Button { - text: "Edit" - visible: modelData.known - onClicked: { - if (chosenSettings) - editorComponent.createObject(null, { - nmSettings: chosenSettings - }); - } - } - } - Loader { - id: failLoader - Layout.alignment: Qt.AlignRight - visible: sourceComponent !== null - } - } - } - } - } + delegate: Component { + Loader { + required property var modelData + width: ListView.view.width + sourceComponent: deviceDelegate } } } diff --git a/src/network/wifi.cpp b/src/network/wifi.cpp index e9939c2..46cf464 100644 --- a/src/network/wifi.cpp +++ b/src/network/wifi.cpp @@ -19,7 +19,8 @@ namespace { QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg); } -WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {}; +WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent) + : Network(std::move(ssid), device, parent) {}; void WifiNetwork::connectWithPsk(const QString& psk) { if (this->bConnected) { @@ -42,9 +43,6 @@ void WifiDevice::setScannerEnabled(bool enabled) { this->bScannerEnabled = enabled; } -void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); } -void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); } - } // namespace qs::network QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) { diff --git a/src/network/wifi.hpp b/src/network/wifi.hpp index c0091f7..cb57ddc 100644 --- a/src/network/wifi.hpp +++ b/src/network/wifi.hpp @@ -7,7 +7,6 @@ #include #include "../core/doc.hpp" -#include "../core/model.hpp" #include "device.hpp" #include "enums.hpp" #include "network.hpp" @@ -27,7 +26,7 @@ class WifiNetwork: public Network { // clang-format on public: - explicit WifiNetwork(QString ssid, QObject* parent = nullptr); + explicit WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent = nullptr); /// Attempt to connect to the network with the given PSK. If the PSK is wrong, /// a @@Network.connectionFailed(s) signal will be emitted with `NoSecrets`. /// @@ -61,9 +60,6 @@ class WifiDevice: public NetworkDevice { QML_UNCREATABLE(""); // clang-format off - /// A list of this available or connected wifi networks. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); /// True when currently scanning for networks. /// When enabled, the scanner populates the device with an active list of available wifi networks. Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled); @@ -74,10 +70,6 @@ class WifiDevice: public NetworkDevice { public: explicit WifiDevice(QObject* parent = nullptr); - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); - - [[nodiscard]] ObjectModel* networks() { return &this->mNetworks; } QBindable bindableScannerEnabled() { return &this->bScannerEnabled; } [[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; } void setScannerEnabled(bool enabled); @@ -88,12 +80,11 @@ signals: void scannerEnabledChanged(bool enabled); private: - ObjectModel mNetworks {this}; Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged); Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged); }; -}; // namespace qs::network +} // namespace qs::network QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network); QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device); diff --git a/src/network/wired.cpp b/src/network/wired.cpp new file mode 100644 index 0000000..66bcd87 --- /dev/null +++ b/src/network/wired.cpp @@ -0,0 +1,22 @@ +#include "wired.hpp" + +#include + +#include "device.hpp" +#include "enums.hpp" +#include "network.hpp" + +namespace qs::network { + +WiredDevice::WiredDevice(QObject* parent): NetworkDevice(DeviceType::Wired, parent) {} + +void WiredDevice::networkAdded(Network* net) { + this->NetworkDevice::networkAdded(net); + this->bNetwork = net; +}; +void WiredDevice::networkRemoved(Network* net) { + this->NetworkDevice::networkRemoved(net); + this->bNetwork = nullptr; +}; + +} // namespace qs::network diff --git a/src/network/wired.hpp b/src/network/wired.hpp new file mode 100644 index 0000000..86fed3a --- /dev/null +++ b/src/network/wired.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "device.hpp" +#include "network.hpp" + +namespace qs::network { + +///! Wired variant of a @@NetworkDevice. +class WiredDevice: public NetworkDevice { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE(""); + + /// The wired network for this device or `null`. + /// + /// > [!NOTE] This network is only available when @@hasLink is `true`. + Q_PROPERTY(Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork); + /// The maximum speed of the physical device link, in megabits per second. + Q_PROPERTY(quint32 linkSpeed READ default NOTIFY linkSpeedChanged BINDABLE bindableLinkSpeed); + /// True if the wired device has a physical link (cable plugged in). + Q_PROPERTY(bool hasLink READ default NOTIFY hasLinkChanged BINDABLE bindableHasLink); + +public: + explicit WiredDevice(QObject* parent = nullptr); + + void networkAdded(Network* net) override; + void networkRemoved(Network* net) override; + + [[nodiscard]] Network* network() const { return this->bNetwork; } + QBindable bindableNetwork() { return &this->bNetwork; } + QBindable bindableLinkSpeed() { return &this->bLinkSpeed; } + QBindable bindableHasLink() { return &this->bHasLink; } + +signals: + void networkChanged(); + void linkSpeedChanged(); + void hasLinkChanged(); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, Network*, bNetwork, &WiredDevice::networkChanged); + Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, quint32, bLinkSpeed, &WiredDevice::linkSpeedChanged); + Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, bool, bHasLink, &WiredDevice::hasLinkChanged); + // clang-format on +}; + +} // namespace qs::network