diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 60750400..03ef86a5 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -4,6 +4,7 @@ qt_add_library(quickshell-network STATIC network.cpp device.cpp wifi.cpp + enums.cpp ) target_include_directories(quickshell-network PRIVATE diff --git a/src/network/device.cpp b/src/network/device.cpp index 22e3949e..9ea9d143 100644 --- a/src/network/device.cpp +++ b/src/network/device.cpp @@ -8,6 +8,7 @@ #include #include "../core/logcat.hpp" +#include "enums.hpp" namespace qs::network { @@ -15,46 +16,6 @@ namespace { QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); } // namespace -QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) { - switch (state) { - case Unknown: return QStringLiteral("Unknown"); - case Connecting: return QStringLiteral("Connecting"); - case Connected: return QStringLiteral("Connected"); - case Disconnecting: return QStringLiteral("Disconnecting"); - case Disconnected: return QStringLiteral("Disconnected"); - default: return QStringLiteral("Unknown"); - } -} - -QString DeviceType::toString(DeviceType::Enum type) { - switch (type) { - case None: return QStringLiteral("None"); - case Wifi: return QStringLiteral("Wifi"); - default: return QStringLiteral("Unknown"); - } -} - -QString NMDeviceState::toString(NMDeviceState::Enum state) { - switch (state) { - case Unknown: return QStringLiteral("Unknown"); - case Unmanaged: return QStringLiteral("Not managed by NetworkManager"); - case Unavailable: return QStringLiteral("Unavailable"); - case Disconnected: return QStringLiteral("Disconnected"); - case Prepare: return QStringLiteral("Preparing to connect"); - case Config: return QStringLiteral("Connecting to a network"); - case NeedAuth: return QStringLiteral("Waiting for authentication"); - case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network"); - case IPCheck: - return QStringLiteral("Checking if further action is required for the requested connection"); - case Secondaries: - return QStringLiteral("Waiting for a required secondary connection to activate"); - case Activated: return QStringLiteral("Connected"); - case Deactivating: return QStringLiteral("Disconnecting"); - case Failed: return QStringLiteral("Failed to connect"); - default: return QStringLiteral("Unknown"); - }; -} - NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) { this->bindableConnected().setBinding([this]() { return this->bState == DeviceConnectionState::Connected; @@ -66,6 +27,11 @@ void NetworkDevice::setAutoconnect(bool autoconnect) { emit this->requestSetAutoconnect(autoconnect); } +void NetworkDevice::setNmManaged(bool managed) { + if (this->bNmManaged == managed) return; + emit this->requestSetNmManaged(managed); +} + void NetworkDevice::disconnect() { if (this->bState == DeviceConnectionState::Disconnected) { qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; diff --git a/src/network/device.hpp b/src/network/device.hpp index f3807c26..8f9cf49e 100644 --- a/src/network/device.hpp +++ b/src/network/device.hpp @@ -6,67 +6,9 @@ #include #include -namespace qs::network { - -///! Connection state of a NetworkDevice. -class DeviceConnectionState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - Disconnected = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state); -}; - -///! Type of network device. -class DeviceType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; +#include "enums.hpp" -public: - enum Enum : quint8 { - None = 0, - Wifi = 1, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceType::Enum type); -}; - -///! NetworkManager-specific device state. -/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState. -class NMDeviceState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMDeviceState::Enum state); -}; +namespace qs::network { ///! A network device. /// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points. @@ -85,9 +27,15 @@ class NetworkDevice: public QObject { Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); /// Connection state of the device. Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - /// A more specific device state when the backend is NetworkManager. + /// A more specific device state. + /// + /// > [!WARNING] Only valid for the NetworkManager backend. Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState); - /// True if the device is allowed to autoconnect. + /// True if the device is managed by NetworkManager. + /// + /// > [!WARNING] Only valid for the NetworkManager backend. + Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged) + /// True if the device is allowed to autoconnect to a network. Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged); // clang-format on @@ -104,18 +52,23 @@ class NetworkDevice: public QObject { QBindable bindableConnected() { return &this->bConnected; }; QBindable bindableState() { return &this->bState; }; QBindable bindableNmState() { return &this->bNmState; }; - [[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }; + QBindable bindableNmManaged() { return &this->bNmManaged; }; + [[nodiscard]] bool nmManaged() { return this->bNmManaged; }; + void setNmManaged(bool managed); QBindable bindableAutoconnect() { return &this->bAutoconnect; }; + [[nodiscard]] bool autoconnect() { return this->bAutoconnect; }; void setAutoconnect(bool autoconnect); signals: void requestDisconnect(); void requestSetAutoconnect(bool autoconnect); + void requestSetNmManaged(bool managed); void nameChanged(); void addressChanged(); void connectedChanged(); void stateChanged(); void nmStateChanged(); + void nmManagedChanged(); void autoconnectChanged(); private: @@ -126,6 +79,7 @@ class NetworkDevice: public QObject { Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged); + Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged); Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged); // clang-format on }; diff --git a/src/network/enums.cpp b/src/network/enums.cpp new file mode 100644 index 00000000..efd56316 --- /dev/null +++ b/src/network/enums.cpp @@ -0,0 +1,117 @@ +#include "enums.hpp" + +#include + +namespace qs::network { + +QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) { + switch (state) { + case Unknown: return QStringLiteral("Unknown"); + case Connecting: return QStringLiteral("Connecting"); + case Connected: return QStringLiteral("Connected"); + case Disconnecting: return QStringLiteral("Disconnecting"); + case Disconnected: return QStringLiteral("Disconnected"); + default: return QStringLiteral("Unknown"); + } +} + +QString DeviceType::toString(DeviceType::Enum type) { + switch (type) { + case None: return QStringLiteral("None"); + case Wifi: return QStringLiteral("Wifi"); + default: return QStringLiteral("Unknown"); + } +} + +QString NMDeviceState::toString(NMDeviceState::Enum state) { + switch (state) { + case Unknown: return QStringLiteral("Unknown"); + case Unmanaged: return QStringLiteral("Not managed by NetworkManager"); + case Unavailable: return QStringLiteral("Unavailable"); + case Disconnected: return QStringLiteral("Disconnected"); + case Prepare: return QStringLiteral("Preparing to connect"); + case Config: return QStringLiteral("Connecting to a network"); + case NeedAuth: return QStringLiteral("Waiting for authentication"); + case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network"); + case IPCheck: + return QStringLiteral("Checking if further action is required for the requested connection"); + case Secondaries: + return QStringLiteral("Waiting for a required secondary connection to activate"); + case Activated: return QStringLiteral("Connected"); + case Deactivating: return QStringLiteral("Disconnecting"); + case Failed: return QStringLiteral("Failed to connect"); + default: return QStringLiteral("Unknown"); + }; +} + +QString NetworkState::toString(NetworkState::Enum state) { + switch (state) { + case NetworkState::Connecting: return QStringLiteral("Connecting"); + case NetworkState::Connected: return QStringLiteral("Connected"); + case NetworkState::Disconnecting: return QStringLiteral("Disconnecting"); + case NetworkState::Disconnected: return QStringLiteral("Disconnected"); + default: return QStringLiteral("Unknown"); + } +} + +QString NMNetworkStateReason::toString(NMNetworkStateReason::Enum reason) { + switch (reason) { + case Unknown: return QStringLiteral("Unknown"); + case None: return QStringLiteral("No reason"); + case UserDisconnected: return QStringLiteral("User disconnection"); + case DeviceDisconnected: + return QStringLiteral("The device the connection was using was disconnected."); + case ServiceStopped: + return QStringLiteral("The service providing the VPN connection was stopped."); + case IpConfigInvalid: + return QStringLiteral("The IP config of the active connection was invalid."); + case ConnectTimeout: + return QStringLiteral("The connection attempt to the VPN service timed out."); + case ServiceStartTimeout: + return QStringLiteral( + "A timeout occurred while starting the service providing the VPN connection." + ); + case ServiceStartFailed: + return QStringLiteral("Starting the service providing the VPN connection failed."); + case NoSecrets: return QStringLiteral("Necessary secrets for the connection were not provided."); + case LoginFailed: return QStringLiteral("Authentication to the server failed."); + case ConnectionRemoved: + return QStringLiteral("Necessary secrets for the connection were not provided."); + case DependencyFailed: + return QStringLiteral("Master connection of this connection failed to activate."); + case DeviceRealizeFailed: return QStringLiteral("Could not create the software device link."); + case DeviceRemoved: return QStringLiteral("The device this connection depended on disappeared."); + default: return QStringLiteral("Unknown"); + }; +}; + +QString WifiSecurityType::toString(WifiSecurityType::Enum type) { + switch (type) { + case Unknown: return QStringLiteral("Unknown"); + case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit"); + case Sae: return QStringLiteral("WPA3"); + case Wpa2Eap: return QStringLiteral("WPA2 Enterprise"); + case Wpa2Psk: return QStringLiteral("WPA2"); + case WpaEap: return QStringLiteral("WPA Enterprise"); + case WpaPsk: return QStringLiteral("WPA"); + case StaticWep: return QStringLiteral("WEP"); + case DynamicWep: return QStringLiteral("Dynamic WEP"); + case Leap: return QStringLiteral("LEAP"); + case Owe: return QStringLiteral("OWE"); + case Open: return QStringLiteral("Open"); + default: return QStringLiteral("Unknown"); + } +} + +QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) { + switch (mode) { + case Unknown: return QStringLiteral("Unknown"); + case AdHoc: return QStringLiteral("Ad-Hoc"); + case Station: return QStringLiteral("Station"); + case AccessPoint: return QStringLiteral("Access Point"); + case Mesh: return QStringLiteral("Mesh"); + default: return QStringLiteral("Unknown"); + }; +} + +} // namespace qs::network diff --git a/src/network/enums.hpp b/src/network/enums.hpp new file mode 100644 index 00000000..cc84d88b --- /dev/null +++ b/src/network/enums.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include +#include +#include + +namespace qs::network { + +///! Connection state of a NetworkDevice. +class DeviceConnectionState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Connecting = 1, + Connected = 2, + Disconnecting = 3, + Disconnected = 4, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state); +}; + +///! Type of network device. +class DeviceType: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + None = 0, + Wifi = 1, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(DeviceType::Enum type); +}; + +///! NetworkManager-specific device state. +/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState. +class NMDeviceState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Unmanaged = 10, + Unavailable = 20, + Disconnected = 30, + Prepare = 40, + Config = 50, + NeedAuth = 60, + IPConfig = 70, + IPCheck = 80, + Secondaries = 90, + Activated = 100, + Deactivating = 110, + Failed = 120, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NMDeviceState::Enum state); +}; + +///! The connection state of a Network. +class NetworkState: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + Connecting = 1, + Connected = 2, + Disconnecting = 3, + Disconnected = 4, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NetworkState::Enum state); +}; + +///! The reason for the NMConnectionState of an NMConnection. +/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason. +class NMNetworkStateReason: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Unknown = 0, + None = 1, + UserDisconnected = 2, + DeviceDisconnected = 3, + ServiceStopped = 4, + IpConfigInvalid = 5, + ConnectTimeout = 6, + ServiceStartTimeout = 7, + ServiceStartFailed = 8, + NoSecrets = 9, + LoginFailed = 10, + ConnectionRemoved = 11, + DependencyFailed = 12, + DeviceRealizeFailed = 13, + DeviceRemoved = 14 + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(NMNetworkStateReason::Enum reason); +}; + +///! The security type of a wifi network. +class WifiSecurityType: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + Wpa3SuiteB192 = 0, + Sae = 1, + Wpa2Eap = 2, + Wpa2Psk = 3, + WpaEap = 4, + WpaPsk = 5, + StaticWep = 6, + DynamicWep = 7, + Leap = 8, + Owe = 9, + Open = 10, + Unknown = 11, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(WifiSecurityType::Enum type); +}; + +///! The 802.11 mode of a wifi device. +class WifiDeviceMode: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_SINGLETON; + +public: + enum Enum : quint8 { + /// The device is part of an Ad-Hoc network without a central access point. + AdHoc = 0, + /// The device is a station that can connect to networks. + Station = 1, + /// The device is a local hotspot/access point. + AccessPoint = 2, + /// The device is an 802.11s mesh point. + Mesh = 3, + /// The device mode is unknown. + Unknown = 4, + }; + Q_ENUM(Enum); + Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode); +}; + +} // namespace qs::network diff --git a/src/network/module.md b/src/network/module.md index a0c8e644..fff19902 100644 --- a/src/network/module.md +++ b/src/network/module.md @@ -4,6 +4,7 @@ headers = [ "network.hpp", "device.hpp", "wifi.hpp", + "enums.hpp", ] ----- This module exposes Network management APIs provided by a supported network backend. diff --git a/src/network/network.cpp b/src/network/network.cpp index e325b051..4f4a0ef5 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -6,27 +6,41 @@ #include #include #include +#include #include "../core/logcat.hpp" #include "device.hpp" +#include "enums.hpp" #include "nm/backend.hpp" +#include "nm/types.hpp" namespace qs::network { namespace { QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); -} // namespace +QS_LOGGING_CATEGORY(logConnection, "quickshell.network.connection", QtWarningMsg); + +bool wpaPskIsValid(const QString& psk) { + if (psk.isEmpty()) return false; + const auto psklen = psk.length(); + + // ASCII passphrase + if (psklen < 8 || psklen > 64) return false; -QString NetworkState::toString(NetworkState::Enum state) { - switch (state) { - case NetworkState::Connecting: return QStringLiteral("Connecting"); - case NetworkState::Connected: return QStringLiteral("Connected"); - case NetworkState::Disconnecting: return QStringLiteral("Disconnecting"); - case NetworkState::Disconnected: return QStringLiteral("Disconnected"); - default: return QStringLiteral("Unknown"); + // Hex PSK + if (psklen == 64) { + for (int i = 0; i < psklen; ++i) { + if (!psk.at(i).isLetterOrNumber()) { + return false; + } + } } + + return true; } +} // namespace + Networking::Networking(QObject* parent): QObject(parent) { // Try to create the NetworkManager backend and bind to it. auto* nm = new NetworkManager(this); @@ -55,6 +69,28 @@ void Networking::setWifiEnabled(bool enabled) { emit this->requestSetWifiEnabled(enabled); } +NMConnection::NMConnection(QObject* parent): QObject(parent) {} + +void NMConnection::updateSettings(const ConnectionSettingsMap& settings) { + emit this->requestUpdateSettings(settings); +} + +void NMConnection::clearSecrets() { emit this->requestClearSecrets(); } + +void NMConnection::forget() { emit this->requestForget(); } + +void NMConnection::setWifiPsk(const QString& psk) { + if (this->bWifiSecurity != WifiSecurityType::WpaPsk + && this->bWifiSecurity != WifiSecurityType::Wpa2Psk) + { + return; + } + if (!wpaPskIsValid(psk)) { + qCWarning(logConnection) << "Malformed PSK provided to" << this; + } + emit this->requestSetWifiPsk(psk); +} + Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) { this->bStateChanging.setBinding([this] { auto state = this->bState.value(); @@ -62,4 +98,63 @@ Network::Network(QString name, QObject* parent): QObject(parent), mName(std::mov }); }; +void Network::setNmDefaultConnection(NMConnection* conn) { + if (this->bNmDefaultConnection == conn) return; + if (!this->mNmConnections.valueList().contains(conn)) return; + emit this->requestSetNmDefaultConnection(conn); +} + +void Network::connect() { + if (this->bConnected) { + qCCritical(logNetwork) << this << "is already connected."; + return; + } + + this->requestConnect(); +} + +void Network::disconnect() { + if (!this->bConnected) { + qCCritical(logNetwork) << this << "is not currently connected"; + return; + } + + this->requestDisconnect(); +} + +void Network::forget() { this->requestForget(); } + +void Network::connectionAdded(NMConnection* conn) { this->mNmConnections.insertObject(conn); } +void Network::connectionRemoved(NMConnection* conn) { this->mNmConnections.removeObject(conn); } + +NMConnectionContext::NMConnectionContext(QObject* parent): QObject(parent) {} + +void NMConnectionContext::setNetwork(Network* network) { + if (this->bNetwork == network) return; + if (this->bNetwork) disconnect(this->bNetwork, nullptr, this, nullptr); + this->bNetwork = network; + + connect(network, &Network::stateChanged, this, [network, this]() { + if (network->state() == NetworkState::Connected) emit this->success(); + }); + connect(network, &Network::stateReasonChanged, this, [network, this]() { + if (network->stateReason() == NMNetworkStateReason::NoSecrets) emit this->noSecrets(); + if (network->stateReason() == NMNetworkStateReason::LoginFailed) emit this->loginFailed(); + }); + connect(network, &Network::destroyed, this, [this]() { this->bNetwork = nullptr; }); +} + } // namespace qs::network + +QDebug operator<<(QDebug debug, const qs::network::NMConnection* connection) { + auto saver = QDebugStateSaver(debug); + + if (connection) { + debug.nospace() << "NMConnection(" << static_cast(connection) + << ", id=" << connection->id() << ")"; + } else { + debug << "NMConnection(nullptr)"; + } + + return debug; +} diff --git a/src/network/network.hpp b/src/network/network.hpp index 8af7c9d8..8f12e49f 100644 --- a/src/network/network.hpp +++ b/src/network/network.hpp @@ -8,27 +8,11 @@ #include "../core/model.hpp" #include "device.hpp" +#include "enums.hpp" +#include "nm/types.hpp" namespace qs::network { -///! The connection state of a Network. -class NetworkState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - Disconnected = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkState::Enum state); -}; - ///! The backend supplying the Network service. class NetworkBackendType: public QObject { Q_OBJECT; @@ -101,19 +85,91 @@ private slots: // clang-format on }; +///! A NetworkManager connection settings profile. +class NMConnection: public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE(""); + + // clang-format off + /// A settings map describing this network configuration. + Q_PROPERTY(ConnectionSettingsMap settings READ default NOTIFY settingsChanged BINDABLE bindableSettings); + /// A settings map describing the secrets belonging to this network configuration. + Q_PROPERTY(ConnectionSettingsMap secretSettings READ default NOTIFY secretSettingsChanged BINDABLE bindableSecretSettings); + /// A human readable unique identifier for the connection. + Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId); + /// The wifi security type of the connection. + /// + /// > [!NOTE] This is only valid for connections to a @@Quickshell.Networking.WifiNetwork + Q_PROPERTY(WifiSecurityType::Enum wifiSecurity READ default NOTIFY wifiSecurityChanged BINDABLE bindableWifiSecurity); + // clang-format on + +public: + explicit NMConnection(QObject* parent = nullptr); + /// Attempt to update the connection with new settings and save the connection to disk. Secrets may be a part of the update request, + /// and will either be stored in persistent storage tor sent to a Secret Agent for storage, depending on the flags + /// associated with each secret and the presence of a registered Secret Agent. + Q_INVOKABLE void updateSettings(const ConnectionSettingsMap& settings); + /// Attempt to clear all of the secrets belonging to this connection. + Q_INVOKABLE void clearSecrets(); + /// Delete the connection. + Q_INVOKABLE void forget(); + /// Set the Pre-Shared-Key secret setting for a connection whos @@wifiSecurity is either + /// @@Quickshell.Networking.WifiSecurityType.WpaPsk or @@Quickshell.Networking.WifiSecurityType.Wpa2Psk. + Q_INVOKABLE void setWifiPsk(const QString& psk); + + QBindable bindableSettings() { return &this->bSettings; } + QBindable bindableSecretSettings() { return &this->bSecretSettings; } + QBindable bindableId() { return &this->bId; } + [[nodiscard]] QString id() const { return this->bId; } + QBindable bindableWifiSecurity() { return &this->bWifiSecurity; } + +signals: + void requestUpdateSettings(ConnectionSettingsMap settings); + void requestClearSecrets(); + void requestForget(); + void requestSetWifiPsk(const QString& psk); + void settingsChanged(); + void secretSettingsChanged(); + void idChanged(); + void wifiSecurityChanged(); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMConnection, ConnectionSettingsMap, bSettings, &NMConnection::settingsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMConnection, ConnectionSettingsMap, bSecretSettings, &NMConnection::secretSettingsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMConnection, QString, bId, &NMConnection::idChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMConnection, WifiSecurityType::Enum, bWifiSecurity, &NMConnection::wifiSecurityChanged); + // clang-format on +}; + ///! A network. class Network: public QObject { Q_OBJECT; QML_ELEMENT; - QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices"); + QML_UNCREATABLE("Network can only be aqcuired through networking devices"); // clang-format off /// The name of the network. Q_PROPERTY(QString name READ name CONSTANT); + /// A list of connnection settings profiles for this network. + /// + /// > [!WARNING] Only valid for the NetworkManager backend. + QSDOC_TYPE_OVERRIDE(ObjectModel*); + Q_PROPERTY(UntypedObjectModel* nmConnections READ nmConnections CONSTANT); + /// The default connection settings profile for this network. This is the connection settings used when connect() is invoked. + /// Only available when the connection is known. + /// + /// > [!WARNING] Only valid for the NetworkManager backend. + Q_PROPERTY(NMConnection* nmDefaultConnection READ nmDefaultConnection WRITE setNmDefaultConnection NOTIFY nmDefaultConnectionChanged BINDABLE bindableNmDefaultConnection); /// True if the network is connected. Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); + /// True if the wifi network has known connection settings saved. + Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown); /// The connectivity state of the network. Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); + /// A specific reason for the connection state. Only available for the NetworkManager backend. + Q_PROPERTY(NMNetworkStateReason::Enum stateReason READ default NOTIFY stateReasonChanged BINDABLE bindableStateReason); /// If the network is currently connecting or disconnecting. Shorthand for checking @@state. Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging); // clang-format on @@ -121,22 +177,89 @@ class Network: public QObject { public: explicit Network(QString name, QObject* parent = nullptr); - [[nodiscard]] QString name() const { return this->mName; }; + /// Attempt to connect to the network. + Q_INVOKABLE void connect(); + /// Disconnect from the network. + Q_INVOKABLE void disconnect(); + /// Forget all connection settings for this network. + Q_INVOKABLE void forget(); + + void connectionAdded(NMConnection* conn); + void connectionRemoved(NMConnection* conn); + + [[nodiscard]] QString name() const { return this->mName; } + [[nodiscard]] ObjectModel* nmConnections() { return &this->mNmConnections; } + [[nodiscard]] NMConnection* nmDefaultConnection() { return this->bNmDefaultConnection; } + QBindable bindableNmDefaultConnection() { return &this->bNmDefaultConnection; } + void setNmDefaultConnection(NMConnection* conn); QBindable bindableConnected() { return &this->bConnected; } + QBindable bindableKnown() { return &this->bKnown; } + [[nodiscard]] NetworkState::Enum state() const { return this->bState; } QBindable bindableState() { return &this->bState; } + [[nodiscard]] NMNetworkStateReason::Enum stateReason() const { return this->bStateReason; } + QBindable bindableStateReason() { return &this->bStateReason; } QBindable bindableStateChanging() { return &this->bStateChanging; } signals: + void requestSetNmDefaultConnection(NMConnection* conn); + void requestConnect(); + void requestDisconnect(); + void requestForget(); + void nmDefaultConnectionChanged(); void connectedChanged(); + void knownChanged(); void stateChanged(); + void stateReasonChanged(); void stateChangingChanged(); protected: QString mName; + ObjectModel mNmConnections {this}; + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(Network, NMConnection*, bNmDefaultConnection, &Network::nmDefaultConnectionChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged); + Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged); + Q_OBJECT_BINDABLE_PROPERTY(Network, NMNetworkStateReason::Enum, bStateReason, &Network::stateReasonChanged); Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged); + // clang-format on +}; + +///! NetworkManager connection context. +/// This is a creatable object and it should be provided a network. +/// It emits helpful signals about the current attempt to connect +/// using the network's nmDefaultConnection. +class NMConnectionContext: public QObject { + Q_OBJECT; + QML_ELEMENT; + + // clang-format off + Q_PROPERTY(Network* network READ network WRITE setNetwork NOTIFY networkChanged) + // clang-format on + +public: + explicit NMConnectionContext(QObject* parent = nullptr); + + [[nodiscard]] Network* network() const { return this->bNetwork; }; + void setNetwork(Network* network); + +signals: + void networkChanged(); + void connectionChanged(); + /// Authentication to the server failed. + void loginFailed(); + /// Necessary secrets for the connection were not provided. + void noSecrets(); + /// The connection attempt was successful. + void success(); + +private: + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMConnectionContext, Network*, bNetwork, &NMConnectionContext::networkChanged); + // clang-format on }; } // namespace qs::network + +QDebug operator<<(QDebug debug, const qs::network::NMConnection* connection); diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt index bb8635e7..653097f9 100644 --- a/src/network/nm/CMakeLists.txt +++ b/src/network/nm/CMakeLists.txt @@ -1,7 +1,7 @@ set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES CLASSNAME DBusNetworkManagerProxy NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp + INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/types.hpp ) qt_add_dbus_interface(NM_DBUS_INTERFACES @@ -42,7 +42,7 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES CLASSNAME DBusNMConnectionSettingsProxy NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp + INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/types.hpp ) qt_add_dbus_interface(NM_DBUS_INTERFACES diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp index 4b61e333..68359b78 100644 --- a/src/network/nm/backend.cpp +++ b/src/network/nm/backend.cpp @@ -19,9 +19,9 @@ #include "../wifi.hpp" #include "dbus_nm_backend.h" #include "dbus_nm_device.h" -#include "dbus_types.hpp" #include "device.hpp" #include "enums.hpp" +#include "types.hpp" #include "wireless.hpp" namespace qs::network { @@ -122,17 +122,12 @@ void NetworkManager::registerDevice(const QString& path) { delete dev; } else { this->mDevices[path] = dev; - // Only register a frontend device while it's managed by NM. - auto onManagedChanged = [this, dev, type](bool managed) { - managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev); - }; // clang-format off QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection); QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection); - QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged); // clang-format on - if (dev->managed()) this->registerFrontendDevice(type, dev); + this->registerFrontendDevice(type, dev); } } temp->deleteLater(); @@ -186,8 +181,10 @@ void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* d frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); }); 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); diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp index 471f57a2..617f6ba9 100644 --- a/src/network/nm/backend.hpp +++ b/src/network/nm/backend.hpp @@ -11,6 +11,8 @@ #include "../network.hpp" #include "dbus_nm_backend.h" #include "device.hpp" +#include "enums.hpp" +#include "types.hpp" namespace qs::network { diff --git a/src/network/nm/connection.cpp b/src/network/nm/connection.cpp index 39b6f66a..6d44861a 100644 --- a/src/network/nm/connection.cpp +++ b/src/network/nm/connection.cpp @@ -14,11 +14,11 @@ #include "../../core/logcat.hpp" #include "../../dbus/properties.hpp" -#include "../wifi.hpp" +#include "../enums.hpp" #include "dbus_nm_active_connection.h" #include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" #include "enums.hpp" +#include "types.hpp" #include "utils.hpp" namespace qs::network { @@ -43,21 +43,24 @@ NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent) return; } - QObject::connect( - this->proxy, - &DBusNMConnectionSettingsProxy::Updated, - this, - &NMConnectionSettings::updateSettings - ); + // clang-format off + QObject::connect(this->proxy, &DBusNMConnectionSettingsProxy::Updated, this, &NMConnectionSettings::getSettings); + QObject::connect(this->proxy, &DBusNMConnectionSettingsProxy::Updated, this, &NMConnectionSettings::getSecrets); + // clang-format on + this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); }); + this->bId.setBinding([this]() { + return this->bSettings.value().value("connection").value("id").toString(); + }); this->connectionSettingsProperties.setInterface(this->proxy); this->connectionSettingsProperties.updateAllViaGetAll(); - this->updateSettings(); + this->getSettings(); + this->getSecrets(); } -void NMConnectionSettings::updateSettings() { +void NMConnectionSettings::getSettings() { auto pending = this->proxy->GetSettings(); auto* call = new QDBusPendingCallWatcher(pending, this); @@ -66,7 +69,7 @@ void NMConnectionSettings::updateSettings() { if (reply.isError()) { qCWarning(logNetworkManager) - << "Failed to get" << this->path() << "settings:" << reply.error().message(); + << "Failed to get settings for" << this->path() << ":" << reply.error().message(); } else { this->bSettings = reply.value(); } @@ -75,6 +78,63 @@ void NMConnectionSettings::updateSettings() { emit this->loaded(); this->mLoaded = true; } + + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NMConnectionSettings::getSecrets() { + auto pending = this->proxy->GetSecrets(""); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply reply = *call; + // We fail silently because this will error if there's no secrets to get + if (!reply.isError()) { + this->bSecretSettings = reply.value(); + } + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NMConnectionSettings::setWifiPsk(const QString& psk) { + auto newSettings = ConnectionSettingsMap(); + newSettings["802-11-wireless-security"]["psk"] = psk; + this->updateSettings(newSettings); +} + +void NMConnectionSettings::updateSettings(const ConnectionSettingsMap& settings) { + auto pending = this->proxy->Update(settings); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply<> reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) + << "Failed to update settings for" << this->path() << ":" << reply.error().message(); + } + delete call; + }; + + QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); +} + +void NMConnectionSettings::clearSecrets() { + auto pending = this->proxy->ClearSecrets(); + auto* call = new QDBusPendingCallWatcher(pending, this); + + auto responseCallback = [this](QDBusPendingCallWatcher* call) { + const QDBusPendingReply<> reply = *call; + + if (reply.isError()) { + qCWarning(logNetworkManager) + << "Failed to clear secrets for" << this->path() << ":" << reply.error().message(); + } delete call; }; @@ -127,7 +187,7 @@ NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QO } void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) { - auto enumReason = static_cast(reason); + auto enumReason = static_cast(reason); if (this->mStateReason == enumReason) return; this->mStateReason = enumReason; emit this->stateReasonChanged(enumReason); diff --git a/src/network/nm/connection.hpp b/src/network/nm/connection.hpp index 4f126c8b..a0816b47 100644 --- a/src/network/nm/connection.hpp +++ b/src/network/nm/connection.hpp @@ -9,11 +9,11 @@ #include #include "../../dbus/properties.hpp" -#include "../wifi.hpp" +#include "../enums.hpp" #include "dbus_nm_active_connection.h" #include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" #include "enums.hpp" +#include "types.hpp" namespace qs::dbus { @@ -36,26 +36,35 @@ class NMConnectionSettings: public QObject { explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr); void forget(); + void updateSettings(const ConnectionSettingsMap& settings); + void clearSecrets(); + void setWifiPsk(const QString& psk); [[nodiscard]] bool isValid() const; [[nodiscard]] QString path() const; [[nodiscard]] QString address() const; [[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; }; + [[nodiscard]] ConnectionSettingsMap secretSettings() const { return this->bSecretSettings; }; [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }; - [[nodiscard]] QBindable bindableSecurity() { return &this->bSecurity; }; + [[nodiscard]] QString id() const { return this->bId; }; signals: void loaded(); void settingsChanged(ConnectionSettingsMap settings); + void secretSettingsChanged(ConnectionSettingsMap settings); void securityChanged(WifiSecurityType::Enum security); - void ssidChanged(QString ssid); + void idChanged(QString id); private: bool mLoaded = false; - void updateSettings(); + void getSettings(); + void getSecrets(); + // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSecretSettings, &NMConnectionSettings::secretSettingsChanged); Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged); + Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, QString, bId, &NMConnectionSettings::idChanged); QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties); // clang-format on @@ -74,20 +83,20 @@ class NMActiveConnection: public QObject { [[nodiscard]] QString address() const; [[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; }; [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }; - [[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; }; + [[nodiscard]] NMNetworkStateReason::Enum stateReason() const { return this->mStateReason; }; signals: void loaded(); void connectionChanged(QDBusObjectPath path); void stateChanged(NMConnectionState::Enum state); - void stateReasonChanged(NMConnectionStateReason::Enum reason); + void stateReasonChanged(NMNetworkStateReason::Enum reason); void uuidChanged(const QString& uuid); private slots: void onStateChanged(quint32 state, quint32 reason); private: - NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown; + NMNetworkStateReason::Enum mStateReason = NMNetworkStateReason::Unknown; // clang-format off Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged); diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp index aad565dd..aac465f1 100644 --- a/src/network/nm/device.cpp +++ b/src/network/nm/device.cpp @@ -14,8 +14,7 @@ #include "../../core/logcat.hpp" #include "../../dbus/properties.hpp" -#include "../device.hpp" -#include "connection.hpp" +#include "../enums.hpp" #include "dbus_nm_device.h" namespace qs::network { @@ -125,6 +124,12 @@ void NMDevice::setAutoconnect(bool autoconnect) { this->pAutoconnect.write(); } +void NMDevice::setManaged(bool managed) { + if (managed == this->bManaged) return; + this->bManaged = managed; + this->pManaged.write(); +} + bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); } QString NMDevice::address() const { return this->deviceProxy ? this->deviceProxy->service() : QString(); diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp index e3ff4b9c..875c5a2b 100644 --- a/src/network/nm/device.hpp +++ b/src/network/nm/device.hpp @@ -8,8 +8,10 @@ #include #include "../../dbus/properties.hpp" +#include "../network.hpp" #include "connection.hpp" #include "dbus_nm_device.h" +#include "types.hpp" namespace qs::dbus { @@ -64,6 +66,7 @@ class NMDevice: public QObject { public slots: void disconnect(); void setAutoconnect(bool autoconnect); + void setManaged(bool managed); private slots: void onAvailableConnectionPathsChanged(const QList& paths); @@ -73,6 +76,7 @@ private slots: void registerConnection(const QString& path); QHash mConnections; + QHash mFrontendConnections; NMActiveConnection* mActiveConnection = nullptr; // clang-format off diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml index 02838473..483484ee 100644 --- a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml +++ b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml @@ -4,6 +4,16 @@ + + + + + + + + + + diff --git a/src/network/nm/dbus_types.hpp b/src/network/nm/types.hpp similarity index 100% rename from src/network/nm/dbus_types.hpp rename to src/network/nm/types.hpp diff --git a/src/network/nm/utils.cpp b/src/network/nm/utils.cpp index 0be29e56..76f175aa 100644 --- a/src/network/nm/utils.cpp +++ b/src/network/nm/utils.cpp @@ -11,17 +11,17 @@ #include #include -#include "../wifi.hpp" -#include "dbus_types.hpp" +#include "../enums.hpp" #include "enums.hpp" +#include "types.hpp" namespace qs::network { WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) { - const QVariantMap& security = settings.value("802-11-wireless-security"); - if (security.isEmpty()) { - return WifiSecurityType::Open; - }; + const QString mapName = "802-11-wireless-security"; + if (!settings.contains(mapName)) return WifiSecurityType::Unknown; + const QVariantMap& security = settings.value(mapName); + if (security.isEmpty()) return WifiSecurityType::Open; const QString keyMgmt = security["key-mgmt"].toString(); const QString authAlg = security["auth-alg"].toString(); @@ -224,6 +224,15 @@ WifiSecurityType::Enum findBestWirelessSecurity( return WifiSecurityType::Unknown; } +ConnectionSettingsMap +mergeSettingsMaps(const ConnectionSettingsMap& target, const ConnectionSettingsMap& source) { + ConnectionSettingsMap result = target; + for (auto iter = source.constBegin(); iter != source.constEnd(); ++iter) { + result[iter.key()].insert(iter.value()); + } + return result; +} + // NOLINTBEGIN QDateTime clockBootTimeToDateTime(qint64 clockBootTime) { clockid_t clkId = CLOCK_BOOTTIME; diff --git a/src/network/nm/utils.hpp b/src/network/nm/utils.hpp index ce8b7841..efbfd690 100644 --- a/src/network/nm/utils.hpp +++ b/src/network/nm/utils.hpp @@ -6,8 +6,8 @@ #include #include "../wifi.hpp" -#include "dbus_types.hpp" #include "enums.hpp" +#include "types.hpp" namespace qs::network { @@ -40,6 +40,9 @@ WifiSecurityType::Enum findBestWirelessSecurity( NM80211ApSecurityFlags::Enum apRsn ); +ConnectionSettingsMap +mergeSettingsMaps(const ConnectionSettingsMap& target, const ConnectionSettingsMap& source); + QDateTime clockBootTimeToDateTime(qint64 clockBootTime); } // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp index 9dff14b3..54c50f3c 100644 --- a/src/network/nm/wireless.cpp +++ b/src/network/nm/wireless.cpp @@ -17,14 +17,15 @@ #include "../../core/logcat.hpp" #include "../../dbus/properties.hpp" +#include "../enums.hpp" #include "../network.hpp" #include "../wifi.hpp" #include "accesspoint.hpp" #include "connection.hpp" #include "dbus_nm_wireless.h" -#include "dbus_types.hpp" #include "device.hpp" #include "enums.hpp" +#include "types.hpp" #include "utils.hpp" namespace qs::network { @@ -39,31 +40,23 @@ NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent) , mSsid(std::move(ssid)) , bKnown(false) , bSecurity(WifiSecurityType::Unknown) - , bReason(NMConnectionStateReason::None) + , bReason(NMNetworkStateReason::None) , bState(NMConnectionState::Deactivated) {} -void NMWirelessNetwork::updateReferenceConnection() { - // If the network has no connections, the reference is nullptr. +// Used by the backend to update the default connection. +void NMWirelessNetwork::updateDefaultConnection() { + // If the network has no connections, the default is nullptr. if (this->mConnections.isEmpty()) { - this->mReferenceConn = nullptr; + this->mDefaultConnection = nullptr; + this->bDefaultFrontendConnection = nullptr; this->bSecurity = WifiSecurityType::Unknown; - // Set security back to reference AP. + // Set the security back to the reference AP. if (this->mReferenceAp) { this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); }); } return; }; - // If the network has an active connection, use it as the reference. - if (this->mActiveConnection) { - auto* conn = this->mConnections.value(this->mActiveConnection->connection().path()); - if (conn && conn != this->mReferenceConn) { - this->mReferenceConn = conn; - this->bSecurity.setBinding([conn]() { return conn->security(); }); - } - return; - } - // Otherwise, choose the connection with the strongest security settings. NMConnectionSettings* selectedConn = nullptr; for (auto* conn: this->mConnections.values()) { @@ -71,12 +64,24 @@ void NMWirelessNetwork::updateReferenceConnection() { selectedConn = conn; } } - if (this->mReferenceConn != selectedConn) { - this->mReferenceConn = selectedConn; + if (this->mDefaultConnection != selectedConn) { + this->mDefaultConnection = selectedConn; + this->bDefaultFrontendConnection = this->mFrontendConnections.value(selectedConn->path()); this->bSecurity.setBinding([selectedConn]() { return selectedConn->security(); }); } } +// Connected the frontend to set the default connection when the user specifies. +void NMWirelessNetwork::setDefaultConnection(NMConnection* frontendConn) { + for (auto it = this->mFrontendConnections.begin(); it != this->mFrontendConnections.end(); ++it) { + if (it.value() == frontendConn) { + this->mDefaultConnection = this->mConnections.value(it.key()); + this->bDefaultFrontendConnection = frontendConn; + return; + } + } +} + void NMWirelessNetwork::updateReferenceAp() { // If the network has no APs, the reference is a nullptr. if (this->mAccessPoints.isEmpty()) { @@ -100,8 +105,7 @@ void NMWirelessNetwork::updateReferenceAp() { if (this->mReferenceAp != selectedAp) { this->mReferenceAp = selectedAp; this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); }); - // Reference AP is used for security when there's no connection settings. - if (!this->mReferenceConn) { + if (!this->mDefaultConnection) { this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); }); } } @@ -126,36 +130,66 @@ void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) { void NMWirelessNetwork::addConnection(NMConnectionSettings* conn) { if (this->mConnections.contains(conn->path())) return; this->mConnections.insert(conn->path(), conn); + this->registerFrontendConnection(conn); + if (!this->mDefaultConnection) { + this->mDefaultConnection = conn; + this->bDefaultFrontendConnection = this->mFrontendConnections.value(conn->path()); + } + auto onDestroyed = [this, conn]() { if (this->mConnections.take(conn->path())) { - this->updateReferenceConnection(); + this->removeFrontendConnection(conn); + this->updateDefaultConnection(); if (this->mConnections.isEmpty()) this->bKnown = false; if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared(); } }; // clang-format off - QObject::connect(conn, &NMConnectionSettings::securityChanged, this, &NMWirelessNetwork::updateReferenceConnection); QObject::connect(conn, &NMConnectionSettings::destroyed, this, onDestroyed); // clang-format on this->bKnown = true; - this->updateReferenceConnection(); }; +void NMWirelessNetwork::registerFrontendConnection(NMConnectionSettings* conn) { + auto* frontendConn = new NMConnection(conn); + + frontendConn->bindableSettings().setBinding([conn]() { return conn->settings(); }); + frontendConn->bindableId().setBinding([conn]() { return conn->id(); }); + frontendConn->bindableWifiSecurity().setBinding([conn]() { return conn->security(); }); + + // clang-format off + QObject::connect(frontendConn, &NMConnection::requestUpdateSettings, conn, &NMConnectionSettings::updateSettings); + QObject::connect(frontendConn, &NMConnection::requestClearSecrets, conn, &NMConnectionSettings::clearSecrets); + QObject::connect(frontendConn, &NMConnection::requestForget, conn, &NMConnectionSettings::forget); + QObject::connect(frontendConn, &NMConnection::requestSetWifiPsk, conn, &NMConnectionSettings::setWifiPsk); + // clang-format on + + this->mFrontendConnections.insert(conn->path(), frontendConn); + emit this->connectionAdded(frontendConn); +} + +void NMWirelessNetwork::removeFrontendConnection(NMConnectionSettings* conn) { + auto* frontendConn = this->mFrontendConnections.take(conn->path()); + if (frontendConn) { + emit this->connectionRemoved(frontendConn); + frontendConn->deleteLater(); + } +} + void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) { if (this->mActiveConnection) return; this->mActiveConnection = active; + this->bState.setBinding([active]() { return active->state(); }); this->bReason.setBinding([active]() { return active->stateReason(); }); auto onDestroyed = [this, active]() { if (this->mActiveConnection && this->mActiveConnection == active) { this->mActiveConnection = nullptr; - this->updateReferenceConnection(); this->bState = NMConnectionState::Deactivated; - this->bReason = NMConnectionStateReason::None; + this->bReason = NMNetworkStateReason::None; } }; QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed); - this->updateReferenceConnection(); }; void NMWirelessNetwork::forget() { @@ -385,16 +419,19 @@ void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) { frontendNet->bindableSignalStrength().setBinding(translateSignal); frontendNet->bindableConnected().setBinding(translateState); frontendNet->bindableKnown().setBinding([net]() { return net->known(); }); - frontendNet->bindableNmReason().setBinding([net]() { return net->reason(); }); + frontendNet->bindableStateReason().setBinding([net]() { return net->reason(); }); frontendNet->bindableSecurity().setBinding([net]() { return net->security(); }); frontendNet->bindableState().setBinding([net]() { return static_cast(net->state()); }); + frontendNet->bindableNmDefaultConnection().setBinding([net]() { + return net->defaultFrontendConn(); + }); QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() { - if (net->referenceConnection()) { + if (net->defaultConn()) { emit this->activateConnection( - QDBusObjectPath(net->referenceConnection()->path()), + QDBusObjectPath(net->defaultConn()->path()), QDBusObjectPath(this->path()) ); return; @@ -408,14 +445,17 @@ void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) { } }); - QObject::connect( - frontendNet, - &WifiNetwork::requestDisconnect, - this, - &NMWirelessDevice::disconnect - ); - + // clang-format off + QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect); + QObject::connect(frontendNet, &WifiNetwork::requestSetNmDefaultConnection, net, &NMWirelessNetwork::setDefaultConnection); QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget); + QObject::connect(net, &NMWirelessNetwork::connectionAdded, frontendNet, &WifiNetwork::connectionAdded); + QObject::connect(net, &NMWirelessNetwork::connectionRemoved, frontendNet, &WifiNetwork::connectionRemoved); + // clang-format on + + for (NMConnection* frontendConn: net->frontendConnections()) { + emit frontendNet->connectionAdded(frontendConn); + }; this->mFrontendNetworks.insert(ssid, frontendNet); emit this->networkAdded(frontendNet); diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp index fe4010eb..43c644c0 100644 --- a/src/network/nm/wireless.hpp +++ b/src/network/nm/wireless.hpp @@ -9,7 +9,6 @@ #include "../wifi.hpp" #include "accesspoint.hpp" -#include "connection.hpp" #include "dbus_nm_wireless.h" #include "device.hpp" #include "enums.hpp" @@ -41,7 +40,10 @@ class NMWirelessNetwork: public QObject { void addAccessPoint(NMAccessPoint* ap); void addConnection(NMConnectionSettings* conn); + void registerFrontendConnection(NMConnectionSettings* conn); + void removeFrontendConnection(NMConnectionSettings* conn); void addActiveConnection(NMActiveConnection* active); + void setDefaultConnection(NMConnection* frontendConn); void forget(); [[nodiscard]] QString ssid() const { return this->mSsid; }; @@ -49,44 +51,55 @@ class NMWirelessNetwork: public QObject { [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }; [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }; [[nodiscard]] bool known() const { return this->bKnown; }; - [[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }; + [[nodiscard]] NMNetworkStateReason::Enum reason() const { return this->bReason; }; [[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; }; - [[nodiscard]] NMConnectionSettings* referenceConnection() const { return this->mReferenceConn; }; [[nodiscard]] QList accessPoints() const { return this->mAccessPoints.values(); }; + [[nodiscard]] NMConnectionSettings* defaultConn() const { return this->mDefaultConnection; }; + [[nodiscard]] NMConnection* defaultFrontendConn() const { + return this->bDefaultFrontendConnection; + }; [[nodiscard]] QList connections() const { return this->mConnections.values(); } + [[nodiscard]] QList frontendConnections() const { + return this->mFrontendConnections.values(); + } [[nodiscard]] QBindable bindableActiveApPath() { return &this->bActiveApPath; }; [[nodiscard]] QBindable bindableVisible() { return &this->bVisible; }; [[nodiscard]] bool visible() const { return this->bVisible; }; signals: void disappeared(); + void connectionAdded(NMConnection* conn); + void connectionRemoved(NMConnection* conn); + void defaultFrontendConnectionChanged(NMConnection* conn); 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 reasonChanged(NMNetworkStateReason::Enum reason); void capabilitiesChanged(NMWirelessCapabilities::Enum caps); void activeApPathChanged(QString path); private: void updateReferenceAp(); - void updateReferenceConnection(); + void updateDefaultConnection(); QString mSsid; QHash mAccessPoints; QHash mConnections; + QHash mFrontendConnections; NMAccessPoint* mReferenceAp = nullptr; - NMConnectionSettings* mReferenceConn = nullptr; NMActiveConnection* mActiveConnection = nullptr; + NMConnectionSettings* mDefaultConnection = nullptr; // clang-format off + Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnection*, bDefaultFrontendConnection, &NMWirelessNetwork::defaultFrontendConnectionChanged); 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, NMNetworkStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged); Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged); Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged); Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged); diff --git a/src/network/test/manual/network.qml b/src/network/test/manual/network.qml index 0fd0f72c..9efd5ce3 100644 --- a/src/network/test/manual/network.qml +++ b/src/network/test/manual/network.qml @@ -51,6 +51,11 @@ FloatingWindow { 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 { @@ -58,7 +63,7 @@ FloatingWindow { color: modelData.connected ? palette.link : palette.placeholderText } Label { - visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting) + visible: (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting) text: `(${NMDeviceState.toString(modelData.nmState)})` } Button { @@ -122,13 +127,34 @@ FloatingWindow { color: palette.placeholderText } } - Label { - visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None) - text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}` + RowLayout { + Text { + text: `Default settings: ` + } + ComboBox { + model: modelData.nmConnections.values.map(conn => conn.id) + currentIndex: modelData.nmConnections.indexOf(modelData.nmDefaultConnection) + onActivated: (index) => { + modelData.nmDefaultConnection = modelData.nmConnections.values[index] + } + } + Button { + text: `Delete` + onClicked: modelData.nmDefaultConnection.forget() + visible: modelData.nmDefaultConnection + } + visible: modelData.known } } RowLayout { Layout.alignment: Qt.AlignRight + Label { + text: NetworkState.toString(modelData.state) + } + Label { + visible: modelData.stateReason != NMNetworkStateReason.None && modelData.stateReason != NMNetworkStateReason.Unknown + text: `(${NMNetworkStateReason.toString(modelData.stateReason)})` + } Button { text: "Connect" onClicked: modelData.connect() diff --git a/src/network/wifi.cpp b/src/network/wifi.cpp index 57fb8ea6..964ff61e 100644 --- a/src/network/wifi.cpp +++ b/src/network/wifi.cpp @@ -2,103 +2,17 @@ #include #include -#include -#include #include #include -#include "../core/logcat.hpp" #include "device.hpp" +#include "enums.hpp" #include "network.hpp" namespace qs::network { -namespace { -QS_LOGGING_CATEGORY(logWifi, "quickshell.network.wifi", QtWarningMsg); -} // namespace - -QString WifiSecurityType::toString(WifiSecurityType::Enum type) { - switch (type) { - case Unknown: return QStringLiteral("Unknown"); - case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit"); - case Sae: return QStringLiteral("WPA3"); - case Wpa2Eap: return QStringLiteral("WPA2 Enterprise"); - case Wpa2Psk: return QStringLiteral("WPA2"); - case WpaEap: return QStringLiteral("WPA Enterprise"); - case WpaPsk: return QStringLiteral("WPA"); - case StaticWep: return QStringLiteral("WEP"); - case DynamicWep: return QStringLiteral("Dynamic WEP"); - case Leap: return QStringLiteral("LEAP"); - case Owe: return QStringLiteral("OWE"); - case Open: return QStringLiteral("Open"); - default: return QStringLiteral("Unknown"); - } -} - -QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) { - switch (mode) { - case Unknown: return QStringLiteral("Unknown"); - case AdHoc: return QStringLiteral("Ad-Hoc"); - case Station: return QStringLiteral("Station"); - case AccessPoint: return QStringLiteral("Access Point"); - case Mesh: return QStringLiteral("Mesh"); - default: return QStringLiteral("Unknown"); - }; -} - -QString NMConnectionStateReason::toString(NMConnectionStateReason::Enum reason) { - switch (reason) { - case Unknown: return QStringLiteral("Unknown"); - case None: return QStringLiteral("No reason"); - case UserDisconnected: return QStringLiteral("User disconnection"); - case DeviceDisconnected: - return QStringLiteral("The device the connection was using was disconnected."); - case ServiceStopped: - return QStringLiteral("The service providing the VPN connection was stopped."); - case IpConfigInvalid: - return QStringLiteral("The IP config of the active connection was invalid."); - case ConnectTimeout: - return QStringLiteral("The connection attempt to the VPN service timed out."); - case ServiceStartTimeout: - return QStringLiteral( - "A timeout occurred while starting the service providing the VPN connection." - ); - case ServiceStartFailed: - return QStringLiteral("Starting the service providing the VPN connection failed."); - case NoSecrets: return QStringLiteral("Necessary secrets for the connection were not provided."); - case LoginFailed: return QStringLiteral("Authentication to the server failed."); - case ConnectionRemoved: - return QStringLiteral("Necessary secrets for the connection were not provided."); - case DependencyFailed: - return QStringLiteral("Master connection of this connection failed to activate."); - case DeviceRealizeFailed: return QStringLiteral("Could not create the software device link."); - case DeviceRemoved: return QStringLiteral("The device this connection depended on disappeared."); - default: return QStringLiteral("Unknown"); - }; -}; - WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {}; -void WifiNetwork::connect() { - if (this->bConnected) { - qCCritical(logWifi) << this << "is already connected."; - return; - } - - this->requestConnect(); -} - -void WifiNetwork::disconnect() { - if (!this->bConnected) { - qCCritical(logWifi) << this << "is not currently connected"; - return; - } - - this->requestDisconnect(); -} - -void WifiNetwork::forget() { this->requestForget(); } - WifiDevice::WifiDevice(QObject* parent): NetworkDevice(DeviceType::Wifi, parent) {}; void WifiDevice::setScannerEnabled(bool enabled) { diff --git a/src/network/wifi.hpp b/src/network/wifi.hpp index 15b093dd..16d5cda3 100644 --- a/src/network/wifi.hpp +++ b/src/network/wifi.hpp @@ -8,87 +8,11 @@ #include "../core/model.hpp" #include "device.hpp" +#include "enums.hpp" #include "network.hpp" namespace qs::network { -///! The security type of a wifi network. -class WifiSecurityType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Wpa3SuiteB192 = 0, - Sae = 1, - Wpa2Eap = 2, - Wpa2Psk = 3, - WpaEap = 4, - WpaPsk = 5, - StaticWep = 6, - DynamicWep = 7, - Leap = 8, - Owe = 9, - Open = 10, - Unknown = 11, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiSecurityType::Enum type); -}; - -///! The 802.11 mode of a wifi device. -class WifiDeviceMode: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - /// The device is part of an Ad-Hoc network without a central access point. - AdHoc = 0, - /// The device is a station that can connect to networks. - Station = 1, - /// The device is a local hotspot/access point. - AccessPoint = 2, - /// The device is an 802.11s mesh point. - Mesh = 3, - /// The device mode is unknown. - Unknown = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode); -}; - -///! NetworkManager-specific reason for a WifiNetworks connection state. -/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason. -class NMConnectionStateReason: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - None = 1, - UserDisconnected = 2, - DeviceDisconnected = 3, - ServiceStopped = 4, - IpConfigInvalid = 5, - ConnectTimeout = 6, - ServiceStartTimeout = 7, - ServiceStartFailed = 8, - NoSecrets = 9, - LoginFailed = 10, - ConnectionRemoved = 11, - DependencyFailed = 12, - DeviceRealizeFailed = 13, - DeviceRemoved = 14 - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMConnectionStateReason::Enum reason); -}; - ///! An available wifi network. class WifiNetwork: public Network { Q_OBJECT; @@ -97,46 +21,23 @@ class WifiNetwork: public Network { // clang-format off /// The current signal strength of the network, from 0.0 to 1.0. Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength); - /// True if the wifi network has known connection settings saved. - Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown); /// The security type of the wifi network. Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity); - /// A specific reason for the connection state when the backend is NetworkManager. - Q_PROPERTY(NMConnectionStateReason::Enum nmReason READ default NOTIFY nmReasonChanged BINDABLE bindableNmReason); // clang-format on public: explicit WifiNetwork(QString ssid, QObject* parent = nullptr); - /// Attempt to connect to the wifi network. - /// - /// > [!WARNING] Quickshell does not yet provide a NetworkManager authentication agent, - /// > meaning another agent will need to be active to enter passwords for unsaved networks. - Q_INVOKABLE void connect(); - /// Disconnect from the wifi network. - Q_INVOKABLE void disconnect(); - /// Forget all connection settings for this wifi network. - Q_INVOKABLE void forget(); - QBindable bindableSignalStrength() { return &this->bSignalStrength; } - QBindable bindableKnown() { return &this->bKnown; } - QBindable bindableNmReason() { return &this->bNmReason; } QBindable bindableSecurity() { return &this->bSecurity; } signals: - void requestConnect(); - void requestDisconnect(); - void requestForget(); void signalStrengthChanged(); - void knownChanged(); void securityChanged(); - void nmReasonChanged(); private: // clang-format off Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, qreal, bSignalStrength, &WifiNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bKnown, &WifiNetwork::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, NMConnectionStateReason::Enum, bNmReason, &WifiNetwork::nmReasonChanged); Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, WifiSecurityType::Enum, bSecurity, &WifiNetwork::securityChanged); // clang-format on };