networking: add wired device support

This commit is contained in:
Carson Powers 2026-04-02 17:02:14 -05:00 committed by outfoxxed
parent 9a54119893
commit d60498adc0
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
31 changed files with 1374 additions and 877 deletions

View file

@ -3,8 +3,10 @@ add_subdirectory(nm)
qt_add_library(quickshell-network STATIC
network.cpp
device.cpp
wired.cpp
wifi.cpp
enums.cpp
qml.cpp
)
target_include_directories(quickshell-network PRIVATE

View file

@ -9,6 +9,7 @@
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
@ -45,4 +46,7 @@ void NetworkDevice::disconnect() {
this->requestDisconnect();
}
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network

View file

@ -7,12 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! A network device.
/// The @@type property may be used to determine if this device is a @@WifiDevice.
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
class NetworkDevice: public QObject {
Q_OBJECT;
QML_ELEMENT;
@ -20,11 +22,17 @@ class NetworkDevice: public QObject {
// clang-format off
/// The device type.
///
/// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
/// When the device type is `Wired`, the device object is a @@WiredDevice.
/// connection and scanning.
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// A list of available or connected networks for this device.
///
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
/// True if the device is connected.
@ -45,6 +53,10 @@ public:
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
virtual void networkAdded(Network* net);
virtual void networkRemoved(Network* net);
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QString name() const { return this->bName; }
@ -69,6 +81,9 @@ signals:
void nmManagedChanged();
void autoconnectChanged();
protected:
ObjectModel<Network> mNetworks {this};
private:
DeviceType::Enum mType;
// clang-format off

View file

@ -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");
}
}

View file

@ -98,6 +98,7 @@ public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);

View file

@ -1,9 +1,11 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"qml.hpp",
"network.hpp",
"device.hpp",
"wifi.hpp",
"wired.hpp",
"enums.hpp",
"nm/settings.hpp",
]

View file

@ -6,12 +6,9 @@
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
#include "nm/settings.hpp"
namespace qs::network {
@ -20,68 +17,10 @@ namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
Network::Network(QString name, NetworkDevice* device, QObject* parent)
: QObject(parent)
, bName(std::move(name))
, mDevice(device) {
this->bStateChanging.setBinding([this] {
auto state = this->bState.value();
return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting;

View file

@ -7,147 +7,14 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
namespace qs::network {
class NetworkDevice;
}
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
namespace qs::network {
///! A network.
/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances.
@ -158,11 +25,13 @@ class Network: public QObject {
// clang-format off
/// The name of the network.
Q_PROPERTY(QString name READ name CONSTANT);
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*> nmSettings READ nmSettings NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
Q_PROPERTY(QList<NMSettings*> nmSettings READ default NOTIFY nmSettingsChanged BINDABLE bindableNmSettings);
/// True if the network is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// True if the wifi network has known connection settings saved.
@ -174,7 +43,7 @@ class Network: public QObject {
// clang-format on
public:
explicit Network(QString name, QObject* parent = nullptr);
explicit Network(QString name, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network.
///
/// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s)
@ -194,7 +63,9 @@ public:
void settingsRemoved(NMSettings* settings);
// clang-format off
[[nodiscard]] QString name() const { return this->mName; }
[[nodiscard]] QString name() const { return this->bName; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] NetworkDevice* device() const { return this->mDevice; }
[[nodiscard]] const QList<NMSettings*>& nmSettings() const { return this->bNmSettings; }
QBindable<QList<NMSettings*>> bindableNmSettings() const { return &this->bNmSettings; }
QBindable<bool> bindableConnected() { return &this->bConnected; }
@ -208,6 +79,7 @@ signals:
/// Signals that a connection to the network has failed because of the given @@ConnectionFailReason.
void connectionFailed(ConnectionFailReason::Enum reason);
void nameChanged();
void connectedChanged();
void knownChanged();
void stateChanged();
@ -219,15 +91,17 @@ signals:
QSDOC_HIDE void requestForget();
protected:
QString mName;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Network, QString, bName, &Network::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, QList<NMSettings*>, bNmSettings, &Network::nmSettingsChanged);
// clang-format on
private:
NetworkDevice* mDevice;
};
} // namespace qs::network

View file

@ -29,6 +29,16 @@ qt_add_dbus_interface(NM_DBUS_INTERFACES
dbus_nm_wireless
)
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wired.xml PROPERTIES
CLASSNAME DBusNMWiredProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Device.Wired.xml
dbus_nm_wired
)
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
CLASSNAME DBusNMAccessPointProxy
NO_NAMESPACE TRUE
@ -67,8 +77,10 @@ qt_add_library(quickshell-network-nm STATIC
settings.cpp
accesspoint.cpp
wireless.cpp
wired.cpp
utils.cpp
dbus_types.cpp
network.cpp
enums.hpp
${NM_DBUS_INTERFACES}
)
@ -79,3 +91,4 @@ target_include_directories(quickshell-network-nm PUBLIC
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
qs_add_link_dependencies(quickshell-network-nm quickshell-network)

View file

@ -7,7 +7,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "../enums.hpp"
#include "dbus_nm_accesspoint.h"
#include "enums.hpp"

View file

@ -15,15 +15,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "wired.hpp"
#include "wireless.hpp"
namespace qs::network {
@ -133,6 +131,7 @@ void NetworkManager::registerDevice(const QString& path) {
switch (type) {
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
case NMDeviceType::Ethernet: dev = new NMWiredDevice(path); break;
default: break;
}
@ -147,8 +146,9 @@ void NetworkManager::registerDevice(const QString& path) {
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
// clang-format on
this->registerFrontendDevice(type, dev);
QObject::connect(dev, &NMDevice::loaded, this, [this, dev]() {
emit this->deviceAdded(dev->frontend());
});
}
} else {
qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path;
@ -160,67 +160,6 @@ void NetworkManager::registerDevice(const QString& path) {
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
}
void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) {
NetworkDevice* frontendDev = nullptr;
switch (type) {
case NMDeviceType::Wifi: {
auto* frontendWifiDev = new WifiDevice(dev);
auto* wifiDev = qobject_cast<NMWirelessDevice*>(dev);
// Bind WifiDevice-specific properties
auto translateMode = [wifiDev]() {
switch (wifiDev->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
// clang-format off
frontendWifiDev->bindableMode().setBinding(translateMode);
wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); });
QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded);
QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved);
// clang-format on
frontendDev = frontendWifiDev;
break;
}
default: return;
}
// Bind generic NetworkDevice properties
auto translateState = [dev]() {
switch (dev->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
frontendDev->bindableState().setBinding(translateState);
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
frontendDev->bindableNmManaged().setBinding([dev]() { return dev->managed(); });
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetNmManaged, dev, &NMDevice::setManaged);
// clang-format on
this->mFrontendDevices.insert(dev->path(), frontendDev);
emit this->deviceAdded(frontendDev);
}
void NetworkManager::removeFrontendDevice(NMDevice* dev) {
auto* frontendDev = this->mFrontendDevices.take(dev->path());
if (frontendDev) {
emit this->deviceRemoved(frontendDev);
frontendDev->deleteLater();
}
}
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
this->registerDevice(path.path());
}
@ -235,7 +174,7 @@ void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
this->mDevices.erase(iter);
if (dev) {
qCDebug(logNetworkManager) << "Device removed:" << path.path();
this->removeFrontendDevice(dev);
emit this->deviceRemoved(dev->frontend());
delete dev;
}
}

View file

@ -8,7 +8,7 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../network.hpp"
#include "../qml.hpp"
#include "dbus_nm_backend.h"
#include "dbus_types.hpp"
#include "device.hpp"
@ -70,11 +70,8 @@ private:
void init();
void registerDevices();
void registerDevice(const QString& path);
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
void removeFrontendDevice(NMDevice* dev);
QHash<QString, NMDevice*> mDevices;
QHash<QString, NetworkDevice*> mFrontendDevices;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);

View file

@ -14,9 +14,13 @@
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../enums.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
@ -49,6 +53,29 @@ NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
this->deviceProperties.updateAllViaGetAll();
}
void NMDevice::bindFrontend(NetworkDevice* frontend) {
auto translateState = [this]() {
switch (this->state()) {
case 0 ... 20: return ConnectionState::Unknown;
case 30: return ConnectionState::Disconnected;
case 40 ... 90: return ConnectionState::Connecting;
case 100: return ConnectionState::Connected;
case 110 ... 120: return ConnectionState::Disconnecting;
}
};
// clang-format off
frontend->bindableName().setBinding([this]() { return this->interface(); });
frontend->bindableAddress().setBinding([this]() { return this->hwAddress(); });
frontend->bindableState().setBinding(translateState);
frontend->bindableAutoconnect().setBinding([this]() { return this->autoconnect(); });
frontend->bindableNmManaged().setBinding([this]() { return this->managed(); });
QObject::connect(frontend, &NetworkDevice::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(frontend, &NetworkDevice::requestSetAutoconnect, this, &NMDevice::setAutoconnect);
QObject::connect(frontend, &NetworkDevice::requestSetNmManaged, this, &NMDevice::setManaged);
QObject::connect(this, &NMDevice::networkAdded, frontend, &NetworkDevice::networkAdded);
QObject::connect(this, &NMDevice::networkRemoved, frontend, &NetworkDevice::networkRemoved);
}
void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) {
auto enumReason = static_cast<NMDeviceStateReason::Enum>(reason);
auto enumNewState = static_cast<NMDeviceState::Enum>(newState);
@ -57,6 +84,22 @@ void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 re
this->bStateReason = enumReason;
}
void NMDevice::bindNetwork(NMNetwork* net) {
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMNetwork::requestDisconnect, this, &NMDevice::disconnect);
QObject::connect(net, &NMNetwork::requestActivateConnection, this, [this](const QString& settingsPath){
emit this->activateConnection(QDBusObjectPath(settingsPath), QDBusObjectPath(this->path()));
});
QObject::connect(net, &NMNetwork::requestAddAndActivateConnection, this, [this](const NMSettingsMap& settingsMap, const QString& specificObject){
emit this->addAndActivateConnection(settingsMap, QDBusObjectPath(this->path()), QDBusObjectPath(specificObject));
});
QObject::connect(net, &NMNetwork::visibilityChanged, this, [this, net](bool visible) {
if (visible) emit this->networkAdded(net->frontend());
else emit this->networkRemoved(net->frontend());
});
if (net->visible()) emit this->networkAdded(net->frontend());
}
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
const QString stringPath = path.path();
@ -160,4 +203,9 @@ DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
}
DBusResult<qs::network::NMDeviceInterfaceFlags::Enum>
DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceInterfaceFlags::Enum>(wire));
}
} // namespace qs::dbus

View file

@ -8,9 +8,11 @@
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../enums.hpp"
#include "../device.hpp"
#include "active_connection.hpp"
#include "dbus_nm_device.h"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@ -22,6 +24,13 @@ struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
static DBusResult<Data> fromWire(Wire wire);
};
template <>
struct DBusDataTransform<qs::network::NMDeviceInterfaceFlags::Enum> {
using Wire = quint32;
using Data = qs::network::NMDeviceInterfaceFlags::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
@ -44,16 +53,23 @@ public:
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }
[[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; }
[[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; }
[[nodiscard]] NMDeviceInterfaceFlags::Enum interfaceFlags() const {
return this->bInterfaceFlags;
}
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }
[[nodiscard]] virtual NetworkDevice* frontend() = 0;
signals:
void loaded();
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const NMSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& apPath
const QDBusObjectPath& specificObjectPath
);
void networkAdded(Network* net);
void networkRemoved(Network* net);
void settingsLoaded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void availableSettingsPathsChanged(QList<QDBusObjectPath> paths);
@ -66,12 +82,17 @@ signals:
void stateReasonChanged(NMDeviceStateReason::Enum reason);
void lastFailReasonChanged(NMDeviceStateReason::Enum reason);
void autoconnectChanged(bool autoconnect);
void interfaceFlagsChanged(NMDeviceInterfaceFlags::Enum flags);
public slots:
void disconnect();
void setAutoconnect(bool autoconnect);
void setManaged(bool managed);
protected:
void bindFrontend(NetworkDevice* frontend);
void bindNetwork(NMNetwork* net);
private slots:
void onStateChanged(quint32 newState, quint32 oldState, quint32 reason);
void onAvailableSettingsPathsChanged(const QList<QDBusObjectPath>& paths);
@ -93,6 +114,7 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableSettingsPathsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceInterfaceFlags::Enum, bInterfaceFlags, &NMDevice::interfaceFlagsChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
@ -102,6 +124,7 @@ private:
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
QS_DBUS_PROPERTY_BINDING(NMDevice, pInterfaceFlags, bInterfaceFlags, deviceProperties, "InterfaceFlags");
// clang-format on
DBusNMDeviceProxy* deviceProxy = nullptr;

View file

@ -183,6 +183,23 @@ public:
Q_ENUM(Enum);
};
// Flags for a network interface.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceInterfaceFlags.
class NMDeviceInterfaceFlags: public QObject {
Q_OBJECT;
public:
enum Enum : quint32 {
None = 0x0,
Up = 0x1,
LowerUp = 0x2,
Promisc = 0x4,
Carrier = 0x10000,
LldpClientEnabled = 0x20000,
};
Q_ENUM(Enum);
};
// 802.11 specific device encryption and authentication capabilities.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
class NMWirelessCapabilities: public QObject {

308
src/network/nm/network.cpp Normal file
View file

@ -0,0 +1,308 @@
#include "network.hpp"
#include <utility>
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qpointer.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "active_connection.hpp"
#include "enums.hpp"
#include "settings.hpp"
#include "utils.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMNetwork::NMNetwork(QObject* parent)
: QObject(parent)
, bKnown(false)
, bReason(NMConnectionStateReason::None)
, bState(NMConnectionState::Deactivated) {}
void NMNetwork::updateReferenceSettings() {
// If the network has no connections, the reference is nullptr.
if (this->mSettings.isEmpty()) {
this->bReferenceSettings = nullptr;
return;
};
// If the network has an active connection, use its settings as the reference.
if (this->mActiveConnection) {
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
if (settings && settings != this->bReferenceSettings) {
this->bReferenceSettings = settings;
}
return;
}
// Otherwise, choose the settings responsible for the last successful connection.
NMSettings* selectedSettings = nullptr;
quint64 selectedTimestamp = 0;
for (auto* settings: this->mSettings.values()) {
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
if (!selectedSettings || timestamp > selectedTimestamp) {
selectedSettings = settings;
selectedTimestamp = timestamp;
}
}
if (this->bReferenceSettings != selectedSettings) {
this->bReferenceSettings = selectedSettings;
}
}
void NMNetwork::addSettings(NMSettings* settings) {
if (this->mSettings.contains(settings->path())) return;
this->mSettings.insert(settings->path(), settings);
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
this->bKnown = true;
this->updateReferenceSettings();
emit this->settingsAdded(settings);
};
void NMNetwork::addActiveConnection(NMActiveConnection* active) {
if (this->mActiveConnection) return;
this->mActiveConnection = active;
this->bState.setBinding([active]() { return active->state(); });
this->bReason.setBinding([active]() { return active->stateReason(); });
auto onDestroyed = [this, active]() {
if (this->mActiveConnection && this->mActiveConnection == active) {
this->mActiveConnection = nullptr;
this->updateReferenceSettings();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceSettings();
};
void NMNetwork::forget() {
if (this->mSettings.isEmpty()) return;
for (auto* conn: this->mSettings.values()) {
conn->forget();
}
}
void NMNetwork::bindFrontend(Network* frontend) {
auto translateState = [this]() { return this->state() == NMConnectionState::Activated; };
frontend->bindableConnected().setBinding(translateState);
frontend->bindableKnown().setBinding([this]() { return this->known(); });
frontend->bindableState().setBinding([this]() {
return static_cast<ConnectionState::Enum>(this->state());
});
frontend->bindableStateChanging().setBinding([this]() {
auto s = static_cast<ConnectionState::Enum>(this->state());
return s == ConnectionState::Connecting || s == ConnectionState::Disconnecting;
});
QObject::connect(this, &NMNetwork::reasonChanged, this, [this, frontend]() {
if (this->reason() == NMConnectionStateReason::DeviceDisconnected) {
auto deviceReason = this->deviceFailReason();
if (deviceReason == NMDeviceStateReason::NoSecrets)
emit frontend->connectionFailed(ConnectionFailReason::NoSecrets);
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
emit frontend->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
emit frontend->connectionFailed(ConnectionFailReason::WifiClientFailed);
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
emit frontend->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
if (deviceReason == NMDeviceStateReason::SsidNotFound)
emit frontend->connectionFailed(ConnectionFailReason::WifiNetworkLost);
}
});
QObject::connect(
frontend,
&Network::requestConnectWithSettings,
this,
[this](NMSettings* settings) {
if (settings) {
emit this->requestActivateConnection(settings->path());
return;
}
qCInfo(
logNetworkManager
) << "Failed to connectWithSettings: The provided settings no longer exist.";
}
);
// clang-format off
QObject::connect(frontend, &Network::requestForget, this, &NMNetwork::forget);
QObject::connect(frontend, &Network::requestDisconnect, this, &NMNetwork::requestDisconnect);
QObject::connect(this, &NMNetwork::settingsAdded, frontend, &Network::settingsAdded);
QObject::connect(this, &NMNetwork::settingsRemoved, frontend, &Network::settingsRemoved);
// clang-format on
}
NMGenericNetwork::NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent)
: NMNetwork(parent)
, mFrontend(new Network(std::move(name), device, this)) {
// Regiter and bind the frontend Network.
this->bindFrontend();
}
void NMGenericNetwork::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMNetwork::bindFrontend(frontend);
QObject::connect(frontend, &Network::requestConnect, this, [this]() {
if (auto* settingsRef = this->referenceSettings()) {
emit this->requestActivateConnection(settingsRef->path());
return;
}
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
return;
});
}
NMWirelessNetwork::NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent)
: NMNetwork(parent)
, mSsid(ssid)
, bSecurity(WifiSecurityType::Unknown) {
auto updateSecurity = [this]() {
if (NMSettings* settings = this->bReferenceSettings) {
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
} else if (NMAccessPoint* ap = this->bReferenceAp) {
this->bSecurity.setBinding([ap]() { return ap->security(); });
} else {
this->bSecurity = WifiSecurityType::Unknown;
}
return;
};
auto checkDisappeared = [this]() {
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
};
QObject::connect(this, &NMWirelessNetwork::referenceSettingsChanged, this, updateSecurity);
QObject::connect(this, &NMWirelessNetwork::referenceApChanged, this, updateSecurity);
QObject::connect(this, &NMWirelessNetwork::settingsRemoved, this, checkDisappeared);
QObject::connect(this, &NMWirelessNetwork::apRemoved, this, checkDisappeared);
// Register and bind the frontend WifiNetwork.
this->mFrontend = new WifiNetwork(ssid, device, this);
this->bindFrontend();
}
void NMWirelessNetwork::updateReferenceAp() {
// If the network has no APs, the reference is a nullptr.
if (this->mAccessPoints.isEmpty()) {
this->bReferenceAp = nullptr;
this->bSignalStrength = 0;
return;
}
// Otherwise, choose the AP with the strongest signal.
NMAccessPoint* selectedAp = nullptr;
for (auto* ap: this->mAccessPoints.values()) {
// Always prefer the active AP.
if (ap->path() == this->bActiveApPath) {
selectedAp = ap;
break;
}
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
selectedAp = ap;
}
}
if (this->bReferenceAp != selectedAp) {
this->bReferenceAp = selectedAp;
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
}
}
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
if (this->mAccessPoints.contains(ap->path())) return;
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
emit this->apRemoved(ap);
this->updateReferenceAp();
}
};
// clang-format off
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
// clang-format on
this->updateReferenceAp();
};
void NMWirelessNetwork::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMNetwork::bindFrontend(frontend);
auto translateSignal = [this]() { return this->signalStrength() / 100.0; };
frontend->bindableSignalStrength().setBinding(translateSignal);
frontend->bindableSecurity().setBinding([this]() { return this->security(); });
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this]() {
if (auto* settingsRef = this->referenceSettings()) {
emit this->requestActivateConnection(settingsRef->path());
return;
}
if (auto* apRef = this->referenceAp()) {
emit this->requestAddAndActivateConnection(NMSettingsMap(), apRef->path());
return;
}
emit this->requestAddAndActivateConnection(NMSettingsMap(), "/");
return;
});
QObject::connect(frontend, &WifiNetwork::requestConnectWithPsk, this, [this](const QString& psk) {
NMSettingsMap settings;
settings["802-11-wireless-security"]["psk"] = psk;
if (const QPointer<NMSettings> ref = this->referenceSettings()) {
auto* call = ref->updateSettings(settings);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this, ref](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager) << "Failed to write PSK: " << reply.error().message();
} else {
if (!ref) {
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The settings disappeared.";
} else {
emit this->requestActivateConnection(ref->path());
}
}
delete call;
}
);
return;
}
if (auto* apRef = this->referenceAp()) {
emit this->requestAddAndActivateConnection(settings, apRef->path());
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithPsk: The network disappeared.";
});
}
} // namespace qs::network

138
src/network/nm/network.hpp Normal file
View file

@ -0,0 +1,138 @@
#pragma once
#include <qobject.h>
#include "../enums.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "accesspoint.hpp"
#include "active_connection.hpp"
#include "enums.hpp"
#include "settings.hpp"
namespace qs::network {
// NMNetwork aggregates NMActiveConnections and NMSettings of the same network.
class NMNetwork: public QObject {
Q_OBJECT;
public:
explicit NMNetwork(QObject* parent = nullptr);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
void connect(const QString& devPath);
void connectWithSettings(const QString& devPath, NMSettings* settings);
// clang-format off
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->bReferenceSettings; }
[[nodiscard]] virtual Network* frontend() = 0;
QBindable<bool> bindableVisible() { return &this->bVisible; }
[[nodiscard]] bool visible() const { return this->bVisible; }
// clang-format on
signals:
void requestDisconnect();
void requestActivateConnection(const QString& settingsPath);
void
requestAddAndActivateConnection(const NMSettingsMap& settingsMap, const QString& specificObject);
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void referenceSettingsChanged(NMSettings* settings);
void visibilityChanged(bool visible);
protected:
void bindFrontend(Network* frontend);
QHash<QString, NMSettings*> mSettings;
Q_OBJECT_BINDABLE_PROPERTY(
NMNetwork,
NMSettings*,
bReferenceSettings,
&NMNetwork::referenceSettingsChanged
);
private:
void updateReferenceSettings();
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bVisible, &NMNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, bool, bKnown, &NMNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionStateReason::Enum, bReason, &NMNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMConnectionState::Enum, bState, &NMNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMNetwork::deviceFailReasonChanged);
// clang-format on
};
// NMGenericNetwork extends NMNetwork to bind and handle the lifetime of a frontend Network.
// This is useful for devices with one network that don't need to extend the base Network class.
class NMGenericNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMGenericNetwork(QString name, NetworkDevice* device, QObject* parent = nullptr);
[[nodiscard]] Network* frontend() override { return this->mFrontend; }
private:
void bindFrontend();
Network* mFrontend;
};
// NMWirelessNetwork extends NMNetwork to also aggregate NMAccessPoints of the same network and scanning functionality.
class NMWirelessNetwork: public NMNetwork {
Q_OBJECT;
public:
explicit NMWirelessNetwork(const QString& ssid, NetworkDevice* device, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->bReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
[[nodiscard]] WifiNetwork* frontend() override { return this->mFrontend; };
// clang-format on
signals:
void disappeared();
void signalStrengthChanged(quint8 signal);
void securityChanged(WifiSecurityType::Enum security);
void activeApPathChanged(QString path);
void referenceApChanged(NMAccessPoint* ap);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void apRemoved(NMAccessPoint* ap);
private:
void updateReferenceAp();
void bindFrontend();
WifiNetwork* mFrontend;
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMAccessPoint*, bReferenceAp, &NMWirelessNetwork::referenceApChanged);
// clang-format on
};
} // namespace qs::network

View file

@ -0,0 +1,4 @@
<node>
<interface name="org.freedesktop.NetworkManager.Device.Wired">
</interface>
</node>

View file

@ -37,6 +37,7 @@ NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) {
qDBusRegisterMetaType<QList<NMIPv6Address>>();
qDBusRegisterMetaType<NMIPv6Route>();
qDBusRegisterMetaType<QList<NMIPv6Route>>();
qDBusRegisterMetaType<QMap<QString, QString>>();
this->proxy = new DBusNMConnectionSettingsProxy(
"org.freedesktop.NetworkManager",

View file

@ -273,6 +273,7 @@ void manualSettingDemarshall(NMSettingsMap& map) {
if (signature == "aa{sv}") return QVariant::fromValue(qdbus_cast<QList<QVariantMap>>(arg));
if (signature == "a(ayuay)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Address>>(arg));
if (signature == "a(ayuayu)") return QVariant::fromValue(qdbus_cast<QList<NMIPv6Route>>(arg));
if (signature == "a{ss}") return QVariant::fromValue(qdbus_cast<QMap<QString, QString>>(arg));
return value;
};
@ -291,7 +292,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
if (s == "802-1x.ca-cert" || s == "802-1x.client-cert" || s == "802-1x.private-key"
|| s == "802-1x.password-raw" || s == "802-1x.phase2-ca-cert"
|| s == "802-1x.phase2-client-cert" || s == "802-1x.phase2-private-key"
|| s == "802-11-wireless.ssid")
|| s == "802-11-wireless.ssid" || s == "802-3-ethernet.cloned-mac-address"
|| s == "802-3-ethernet.mac-address")
{
if (value.typeId() == QMetaType::QString) {
return value.toString().toUtf8();
@ -423,7 +425,8 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
// QVariantList -> QStringList
if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search"
|| s == "802-11-wireless.seen-bssids")
|| s == "802-11-wireless.seen-bssids" || s == "802-3-ethernet.mac-address-blacklist"
|| s == "802-3-ethernet.mac-address-denylist" || s == "802-3-ethernet.s390-subchannels")
{
if (value.typeId() == QMetaType::QVariantList) {
QStringList stringList;
@ -435,6 +438,18 @@ QVariant settingTypeFromQml(const QString& group, const QString& key, const QVar
return QVariant();
}
// QVariantMap -> QMap<QString, QString>
if (s == "802-3-ethernet.s390-options") {
if (value.canConvert<QVariantMap>()) {
QMap<QString, QString> r;
for (const auto& [key, val]: value.toMap().asKeyValueRange()) {
r.insert(key, val.toString());
}
return QVariant::fromValue(r);
}
return QVariant();
}
// double (whole number) -> qint32
if (value.typeId() == QMetaType::Double) {
auto num = value.toDouble();
@ -498,6 +513,15 @@ QVariant settingTypeToQml(const QVariant& value) {
return out;
}
// QMap<QString, QString> -> QVariantMap
if (value.userType() == qMetaTypeId<QMap<QString, QString>>()) {
QVariantMap out;
for (const auto& [key, val]: value.value<QMap<QString, QString>>().asKeyValueRange()) {
out.insert(key, val);
}
return out;
}
return value;
}

101
src/network/nm/wired.cpp Normal file
View file

@ -0,0 +1,101 @@
#include "wired.hpp"
#include <qdbusconnection.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWiredDevice::NMWiredDevice(const QString& path, QObject* parent): NMDevice(path, parent) {
this->wiredProxy = new DBusNMWiredProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->wiredProxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for wired device at" << path;
return;
}
// Wait to create the NMGenericNetwork/Network
// until the dbus properties load because the network requires the interface name.
QObject::connect(
&this->wiredProperties,
&DBusPropertyGroup::getAllFinished,
this,
&NMWiredDevice::initWired,
Qt::SingleShotConnection
);
this->wiredProperties.setInterface(this->wiredProxy);
this->wiredProperties.updateAllViaGetAll();
// Register and bind the frontend WiredDevice.
this->mFrontend = new WiredDevice(this);
this->bindFrontend();
};
void NMWiredDevice::initWired() {
// Register the NMGenericNetwork and bind the frontend Network.
// For wired networking, there is only one Network and it should exist for the lifetime of the device.
auto* net = new NMGenericNetwork(QString(), this->frontend(), this);
net->frontend()->bindableName().setBinding([this]() { return this->interface(); });
auto visible = [this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
};
net->bindableVisible().setBinding(visible);
this->NMDevice::bindNetwork(net);
this->mNetwork = net;
// clang-format off
QObject::connect(this, &NMWiredDevice::settingsLoaded, this, &NMWiredDevice::onSettingsLoaded);
QObject::connect(this, &NMWiredDevice::activeConnectionLoaded, this, &NMWiredDevice::onActiveConnectionLoaded);
// clang-format on
emit this->loaded();
}
void NMWiredDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
frontend->bindableLinkSpeed().setBinding([this]() { return this->bSpeed.value(); });
frontend->bindableHasLink().setBinding([this]() {
return (this->interfaceFlags() & NMDeviceInterfaceFlags::Carrier) != 0;
});
}
void NMWiredDevice::onSettingsLoaded(NMSettings* settings) {
this->mNetwork->addSettings(settings);
}
void NMWiredDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
this->mNetwork->addActiveConnection(active);
}
bool NMWiredDevice::isValid() const {
return this->NMDevice::isValid() && (this->wiredProxy && this->wiredProxy->isValid());
}
} // namespace qs::network

53
src/network/nm/wired.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include <qdbusextratypes.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../wired.hpp"
#include "active_connection.hpp"
#include "dbus_nm_wired.h"
#include "device.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wired interface
// Owns the lifetime of a NMGenericNetwork, and a frontend WiredDevice.
class NMWiredDevice: public NMDevice {
Q_OBJECT;
public:
explicit NMWiredDevice(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const override;
[[nodiscard]] WiredDevice* frontend() override { return this->mFrontend; };
signals:
void speedChanged(quint32 speed);
private slots:
void onSettingsLoaded(NMSettings* settings);
void onActiveConnectionLoaded(NMActiveConnection* active);
private:
void initWired();
void bindFrontend();
WiredDevice* mFrontend = nullptr;
NMGenericNetwork* mNetwork = nullptr;
Q_OBJECT_BINDABLE_PROPERTY(NMWiredDevice, quint32, bSpeed, &NMWiredDevice::speedChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wiredProperties);
QS_DBUS_PROPERTY_BINDING(NMWiredDevice, pSpeed, bSpeed, wiredProperties, "Speed");
DBusNMWiredProxy* wiredProxy = nullptr;
};
}; // namespace qs::network

View file

@ -1,5 +1,4 @@
#include "wireless.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qdatetime.h>
@ -12,7 +11,6 @@
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qpointer.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@ -28,6 +26,7 @@
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
#include "utils.hpp"
@ -38,143 +37,6 @@ namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent)
: QObject(parent)
, mSsid(std::move(ssid))
, bKnown(false)
, bSecurity(WifiSecurityType::Unknown)
, bReason(NMConnectionStateReason::None)
, bState(NMConnectionState::Deactivated) {}
void NMWirelessNetwork::updateReferenceSettings() {
// If the network has no connections, the reference is nullptr.
if (this->mSettings.isEmpty()) {
this->mReferenceSettings = nullptr;
this->bSecurity = WifiSecurityType::Unknown;
if (this->mReferenceAp) {
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
}
return;
};
// If the network has an active connection, use its settings as the reference.
if (this->mActiveConnection) {
auto* settings = this->mSettings.value(this->mActiveConnection->connection().path());
if (settings && settings != this->mReferenceSettings) {
this->mReferenceSettings = settings;
this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); });
}
return;
}
// Otherwise, choose the settings responsible for the last successful connection.
NMSettings* selectedSettings = nullptr;
quint64 selectedTimestamp = 0;
for (auto* settings: this->mSettings.values()) {
const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong();
if (!selectedSettings || timestamp > selectedTimestamp) {
selectedSettings = settings;
selectedTimestamp = timestamp;
}
}
if (this->mReferenceSettings != selectedSettings) {
this->mReferenceSettings = selectedSettings;
this->bSecurity.setBinding([selectedSettings]() {
return securityFromSettingsMap(selectedSettings->map());
});
}
}
void NMWirelessNetwork::updateReferenceAp() {
// If the network has no APs, the reference is a nullptr.
if (this->mAccessPoints.isEmpty()) {
this->mReferenceAp = nullptr;
this->bSignalStrength = 0;
return;
}
// Otherwise, choose the AP with the strongest signal.
NMAccessPoint* selectedAp = nullptr;
for (auto* ap: this->mAccessPoints.values()) {
// Always prefer the active AP.
if (ap->path() == this->bActiveApPath) {
selectedAp = ap;
break;
}
if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) {
selectedAp = ap;
}
}
if (this->mReferenceAp != selectedAp) {
this->mReferenceAp = selectedAp;
this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); });
// Reference AP is used for security when there's no connection settings.
if (!this->mReferenceSettings) {
this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); });
}
}
}
void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) {
if (this->mAccessPoints.contains(ap->path())) return;
this->mAccessPoints.insert(ap->path(), ap);
auto onDestroyed = [this, ap]() {
if (this->mAccessPoints.take(ap->path())) {
this->updateReferenceAp();
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
// clang-format off
QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp);
QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed);
// clang-format on
this->updateReferenceAp();
};
void NMWirelessNetwork::addSettings(NMSettings* settings) {
if (this->mSettings.contains(settings->path())) return;
this->mSettings.insert(settings->path(), settings);
auto onDestroyed = [this, settings]() {
if (this->mSettings.take(settings->path())) {
emit this->settingsRemoved(settings);
this->updateReferenceSettings();
if (this->mSettings.isEmpty()) this->bKnown = false;
if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared();
}
};
QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed);
this->bKnown = true;
this->updateReferenceSettings();
emit this->settingsAdded(settings);
};
void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) {
if (this->mActiveConnection) return;
this->mActiveConnection = active;
this->bState.setBinding([active]() { return active->state(); });
this->bReason.setBinding([active]() { return active->stateReason(); });
auto onDestroyed = [this, active]() {
if (this->mActiveConnection && this->mActiveConnection == active) {
this->mActiveConnection = nullptr;
this->updateReferenceSettings();
this->bState = NMConnectionState::Deactivated;
this->bReason = NMConnectionStateReason::None;
}
};
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
this->updateReferenceSettings();
};
void NMWirelessNetwork::forget() {
if (this->mSettings.isEmpty()) return;
for (auto* conn: this->mSettings.values()) {
conn->forget();
}
}
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
: NMDevice(path, parent)
, mScanTimer(this) {
@ -203,6 +65,10 @@ NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
this->wirelessProperties.setInterface(this->wirelessProxy);
this->wirelessProperties.updateAllViaGetAll();
// Register and bind the frontend WifiDevice.
this->mFrontend = new WifiDevice(this);
this->bindFrontend();
}
void NMWirelessDevice::initWireless() {
@ -214,7 +80,9 @@ void NMWirelessDevice::initWireless() {
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged);
// clang-format on
this->registerAccessPoints();
emit this->loaded();
}
void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) {
@ -364,176 +232,45 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
}
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
auto* net = new NMWirelessNetwork(ssid, this);
auto* net = new NMWirelessNetwork(ssid, this->frontend(), this);
this->NMDevice::bindNetwork(net);
auto visible = [this, net]() {
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
};
net->bindableVisible().setBinding(visible);
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); });
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
this->mNetworks.insert(ssid, net);
this->registerFrontendNetwork(net);
qCDebug(logNetworkManager) << "Registered network for SSID" << ssid;
return net;
}
void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) {
auto ssid = net->ssid();
auto* frontendNet = new WifiNetwork(ssid, net);
// Bind WifiNetwork to NMWirelessNetwork
auto translateSignal = [net]() { return net->signalStrength() / 100.0; };
auto translateState = [net]() { return net->state() == NMConnectionState::Activated; };
frontendNet->bindableSignalStrength().setBinding(translateSignal);
frontendNet->bindableConnected().setBinding(translateState);
frontendNet->bindableKnown().setBinding([net]() { return net->known(); });
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
frontendNet->bindableState().setBinding([net]() {
return static_cast<ConnectionState::Enum>(net->state());
});
QObject::connect(net, &NMWirelessNetwork::reasonChanged, this, [net, frontendNet]() {
if (net->reason() == NMConnectionStateReason::DeviceDisconnected) {
auto deviceReason = net->deviceFailReason();
if (deviceReason == NMDeviceStateReason::NoSecrets)
emit frontendNet->connectionFailed(ConnectionFailReason::NoSecrets);
if (deviceReason == NMDeviceStateReason::SupplicantDisconnect)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientDisconnected);
if (deviceReason == NMDeviceStateReason::SupplicantFailed)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientFailed);
if (deviceReason == NMDeviceStateReason::SupplicantTimeout)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiAuthTimeout);
if (deviceReason == NMDeviceStateReason::SsidNotFound)
emit frontendNet->connectionFailed(ConnectionFailReason::WifiNetworkLost);
}
});
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
if (net->referenceSettings()) {
emit this->activateConnection(
QDBusObjectPath(net->referenceSettings()->path()),
QDBusObjectPath(this->path())
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
NMSettingsMap(),
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connect to"
<< this->path() + ": The network disappeared.";
});
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithPsk,
this,
[this, net](const QString& psk) {
NMSettingsMap settings;
settings["802-11-wireless-security"]["psk"] = psk;
if (const QPointer<NMSettings> ref = net->referenceSettings()) {
auto* call = ref->updateSettings(settings);
QObject::connect(
call,
&QDBusPendingCallWatcher::finished,
this,
[this, ref](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<> reply = *call;
if (reply.isError()) {
qCInfo(logNetworkManager)
<< "Failed to write PSK for" << this->path() + ":" << reply.error().message();
} else {
if (!ref) {
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The settings disappeared.";
} else {
emit this->activateConnection(
QDBusObjectPath(ref->path()),
QDBusObjectPath(this->path())
);
}
}
delete call;
}
);
return;
}
if (net->referenceAp()) {
emit this->addAndActivateConnection(
settings,
QDBusObjectPath(this->path()),
QDBusObjectPath(net->referenceAp()->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithPsk to"
<< this->path() + ": The network disappeared.";
}
);
QObject::connect(
frontendNet,
&WifiNetwork::requestConnectWithSettings,
this,
[this](NMSettings* settings) {
if (settings) {
emit this->activateConnection(
QDBusObjectPath(settings->path()),
QDBusObjectPath(this->path())
);
return;
}
qCInfo(logNetworkManager) << "Failed to connectWithSettings to"
<< this->path() + ": The provided settings no longer exist.";
}
);
QObject::connect(
net,
&NMWirelessNetwork::visibilityChanged,
this,
[this, frontendNet](bool visible) {
if (visible) this->networkAdded(frontendNet);
else this->networkRemoved(frontendNet);
}
);
// clang-format off
QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect);
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
QObject::connect(net, &NMWirelessNetwork::settingsAdded, frontendNet, &WifiNetwork::settingsAdded);
QObject::connect(net, &NMWirelessNetwork::settingsRemoved, frontendNet, &WifiNetwork::settingsRemoved);
// clang-format on
this->mFrontendNetworks.insert(ssid, frontendNet);
if (net->visible()) emit this->networkAdded(frontendNet);
}
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
if (frontendNet) {
if (net->visible()) emit this->networkRemoved(frontendNet);
frontendNet->deleteLater();
}
}
void NMWirelessDevice::removeNetwork() {
auto* net = qobject_cast<NMWirelessNetwork*>(this->sender());
if (this->mNetworks.take(net->ssid())) {
this->removeFrontendNetwork(net);
if (net->visible()) emit this->networkRemoved(net->frontend());
delete net;
};
}
void NMWirelessDevice::bindFrontend() {
auto* frontend = this->mFrontend;
this->NMDevice::bindFrontend(frontend);
auto translateMode = [this]() {
switch (this->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
frontend->bindableMode().setBinding(translateMode);
this->bindableScanning().setBinding([frontend]() { return frontend->scannerEnabled(); });
}
bool NMWirelessDevice::isValid() const {
return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid());
}

View file

@ -13,6 +13,7 @@
#include "dbus_nm_wireless.h"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
#include "settings.hpp"
namespace qs::dbus {
@ -33,76 +34,9 @@ struct DBusDataTransform<QDateTime> {
} // namespace qs::dbus
namespace qs::network {
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMSettings objects.
class NMWirelessNetwork: public QObject {
Q_OBJECT;
public:
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
void addAccessPoint(NMAccessPoint* ap);
void addSettings(NMSettings* settings);
void addActiveConnection(NMActiveConnection* active);
void forget();
// clang-format off
[[nodiscard]] QString ssid() const { return this->mSsid; }
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }
[[nodiscard]] bool known() const { return this->bKnown; }
[[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }
QBindable<NMDeviceStateReason::Enum> bindableDeviceFailReason() { return &this->bDeviceFailReason; }
[[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; }
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; }
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); }
[[nodiscard]] QList<NMSettings*> settings() const { return this->mSettings.values(); }
[[nodiscard]] NMSettings* referenceSettings() const { return this->mReferenceSettings; }
QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; }
QBindable<bool> bindableVisible() { return &this->bVisible; }
bool visible() const { return this->bVisible; }
// clang-format on
signals:
void disappeared();
void settingsAdded(NMSettings* settings);
void settingsRemoved(NMSettings* settings);
void visibilityChanged(bool visible);
void signalStrengthChanged(quint8 signal);
void stateChanged(NMConnectionState::Enum state);
void knownChanged(bool known);
void securityChanged(WifiSecurityType::Enum security);
void reasonChanged(NMConnectionStateReason::Enum reason);
void deviceFailReasonChanged(NMDeviceStateReason::Enum reason);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
void activeApPathChanged(QString path);
private:
void updateReferenceAp();
void updateReferenceSettings();
QString mSsid;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMSettings*> mSettings;
NMAccessPoint* mReferenceAp = nullptr;
NMSettings* mReferenceSettings = nullptr;
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMWirelessNetwork::deviceFailReasonChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged);
// clang-format on
};
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s).
// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), and a frontend WifiDevice.
class NMWirelessDevice: public NMDevice {
Q_OBJECT;
@ -114,12 +48,11 @@ public:
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; }
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; }
[[nodiscard]] WifiDevice* frontend() override { return this->mFrontend; };
signals:
void accessPointLoaded(NMAccessPoint* ap);
void accessPointRemoved(NMAccessPoint* ap);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
void lastScanChanged(QDateTime lastScan);
void scanningChanged(bool scanning);
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
@ -137,17 +70,16 @@ private slots:
private:
void registerAccessPoint(const QString& path);
void registerFrontendNetwork(NMWirelessNetwork* net);
void removeFrontendNetwork(NMWirelessNetwork* net);
void removeNetwork();
bool checkVisibility(WifiNetwork* net);
void registerAccessPoints();
void initWireless();
void bindFrontend();
NMWirelessNetwork* registerNetwork(const QString& ssid);
WifiDevice* mFrontend;
QHash<QString, NMAccessPoint*> mAccessPoints;
QHash<QString, NMWirelessNetwork*> mNetworks;
QHash<QString, WifiNetwork*> mFrontendNetworks;
QDateTime mLastScanRequest;
QTimer mScanTimer;

82
src/network/qml.cpp Normal file
View file

@ -0,0 +1,82 @@
#include "qml.hpp"
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "nm/backend.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
// clang-format off
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled);
QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); });
this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); });
this->bindableConnectivity().setBinding([nm]() { return static_cast<NetworkConnectivity::Enum>(nm->connectivity()); });
// clang-format on
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
Networking* Networking::instance() {
static Networking* instance = new Networking(); // NOLINT
return instance;
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::checkConnectivity() {
if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return;
emit this->requestCheckConnectivity();
}
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
void Networking::setConnectivityCheckEnabled(bool enabled) {
if (this->bConnectivityCheckEnabled == enabled) return;
emit this->requestSetConnectivityCheckEnabled(enabled);
}
NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) {
// clang-format off
QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged);
QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged);
QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged);
QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged);
QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged);
// clang-format on
}
void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); }
} // namespace qs::network

151
src/network/qml.hpp Normal file
View file

@ -0,0 +1,151 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
namespace qs::network {
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
class Networking: public QObject {
Q_OBJECT;
public:
static Networking* instance();
void checkConnectivity();
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; }
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; }
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }
QBindable<bool> bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; }
QBindable<bool> bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; }
[[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; }
void setConnectivityCheckEnabled(bool enabled);
QBindable<NetworkConnectivity::Enum> bindableConnectivity() { return &this->bConnectivity; }
signals:
void requestSetWifiEnabled(bool enabled);
void requestSetConnectivityCheckEnabled(bool enabled);
void requestCheckConnectivity();
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
explicit Networking(QObject* parent = nullptr);
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged);
// clang-format on
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class NetworkingQml: public QObject {
Q_OBJECT;
QML_NAMED_ELEMENT(Networking);
QML_SINGLETON;
// clang-format off
/// A list of all network devices. Networks are exposed through their respective devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
/// True if the @@backend supports connectivity checks.
Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity);
/// True if connectivity checking is enabled.
Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged);
/// The result of the last connectivity check.
///
/// Connectivity checks may require additional configuration depending on your distro.
///
/// > [!NOTE] This property can be used to determine if network access is restricted
/// > or gated behind a captive portal.
/// >
/// > If checking for captive portals, @@checkConnectivity() should be called after
/// > the portal is dismissed to update this property.
Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity);
// clang-format on
public:
explicit NetworkingQml(QObject* parent = nullptr);
/// Re-check the network connectivity state immediately.
/// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal.
Q_INVOKABLE static void checkConnectivity();
[[nodiscard]] static ObjectModel<NetworkDevice>* devices() {
return Networking::instance()->devices();
}
[[nodiscard]] static NetworkBackendType::Enum backend() {
return Networking::instance()->backend();
}
[[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); }
static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); }
[[nodiscard]] static QBindable<bool> bindableWifiHardwareEnabled() {
return Networking::instance()->bindableWifiHardwareEnabled();
}
[[nodiscard]] static QBindable<bool> bindableWifiEnabled() {
return Networking::instance()->bindableWifiEnabled();
}
[[nodiscard]] static QBindable<bool> bindableCanCheckConnectivity() {
return Networking::instance()->bindableCanCheckConnectivity();
}
[[nodiscard]] static bool connectivityCheckEnabled() {
return Networking::instance()->connectivityCheckEnabled();
}
static void setConnectivityCheckEnabled(bool enabled) {
Networking::instance()->setConnectivityCheckEnabled(enabled);
}
[[nodiscard]] static QBindable<NetworkConnectivity::Enum> bindableConnectivity() {
return Networking::instance()->bindableConnectivity();
}
signals:
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
void canCheckConnectivityChanged();
void connectivityCheckEnabledChanged();
void connectivityChanged();
};
} // namespace qs::network

View file

@ -73,6 +73,241 @@ Scope {
}
}
Component {
id: deviceDelegate
WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
RowLayout {
visible: modelData.type === DeviceType.Wired
CheckBox {
text: "Link connected"
checked: modelData.hasLink
enabled: false
}
Label {
text: `Link max speed: ${modelData.linkSpeed} Mbps`
}
}
RowLayout {
visible: modelData.type === DeviceType.Wifi
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
}
Repeater {
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
if (modelData.device?.type === DeviceType.Wifi) {
return b.signalStrength - a.signalStrength;
}
})
}
WrapperRectangle {
id: ethernetNetwork
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
visible: modelData.device?.type === DeviceType.Wifi
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (ethernetNetwork.chosenSettings)
modelData.connectWithSettings(ethernetNetwork.chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (ethernetNetwork.chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
}
}
}
FloatingWindow {
color: contentItem.palette.window
@ -139,219 +374,11 @@ Scope {
Layout.fillWidth: true
Layout.fillHeight: true
model: Networking.devices
delegate: WrapperRectangle {
width: parent.width
color: "transparent"
border.color: palette.button
border.width: 1
margin: 5
ColumnLayout {
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.address
}
Label {
text: `(Type: ${DeviceType.toString(modelData.type)})`
}
CheckBox {
text: `Managed`
checked: modelData.nmManaged
onClicked: modelData.nmManaged = !modelData.nmManaged
}
}
RowLayout {
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
Button {
visible: modelData.state == ConnectionState.Connected
text: "Disconnect"
onClicked: modelData.disconnect()
}
CheckBox {
text: "Autoconnect"
checked: modelData.autoconnect
onClicked: modelData.autoconnect = !modelData.autoconnect
}
Label {
text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}`
visible: modelData.type == DeviceType.Wifi
}
CheckBox {
text: "Scanner"
checked: modelData.scannerEnabled
onClicked: modelData.scannerEnabled = !modelData.scannerEnabled
visible: modelData.type === DeviceType.Wifi
}
}
Repeater {
Layout.fillWidth: true
model: ScriptModel {
values: [...modelData.networks.values].sort((a, b) => {
if (a.connected !== b.connected) {
return b.connected - a.connected;
}
return b.signalStrength - a.signalStrength;
})
}
WrapperRectangle {
property var chosenSettings: {
const settings = modelData.nmSettings;
if (!settings || settings.length === 0) {
return null;
}
if (settings.length === 1) {
return settings[0];
}
return settings[settingsComboBox.currentIndex];
}
Connections {
target: modelData
function onConnectionFailed(reason) {
failLoader.sourceComponent = failComponent;
failLoader.item.failReason = reason;
}
function onStateChanged() {
if (modelData.state == ConnectionState.Connecting) {
failLoader.sourceComponent = null;
}
}
}
Component {
id: failComponent
RowLayout {
property var failReason
Label {
text: ConnectionFailReason.toString(failReason)
}
RowLayout {
TextField {
id: pskField
placeholderText: "PSK"
}
Button {
text: "Set"
visible: pskField.visible
onClicked: {
modelData.connectWithPsk(pskField.text);
failLoader.sourceComponent = null;
}
}
visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae
}
Button {
text: "Close"
onClicked: failLoader.sourceComponent = null
}
}
}
Layout.fillWidth: true
color: modelData.connected ? palette.highlight : palette.button
border.color: palette.mid
border.width: 1
margin: 5
RowLayout {
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Label {
text: modelData.name
font.bold: true
}
Label {
text: modelData.known ? "Known" : ""
color: palette.placeholderText
}
}
RowLayout {
Label {
text: `Security: ${WifiSecurityType.toString(modelData.security)}`
color: palette.placeholderText
}
Label {
text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%`
color: palette.placeholderText
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignRight
RowLayout {
Layout.alignment: Qt.AlignRight
BusyIndicator {
implicitHeight: 30
implicitWidth: 30
running: modelData.stateChanging
visible: modelData.stateChanging
}
Label {
text: ConnectionState.toString(modelData.state)
color: modelData.connected ? palette.link : palette.placeholderText
}
RowLayout {
Label {
text: "Choose settings:"
}
ComboBox {
id: settingsComboBox
model: modelData.nmSettings.map(s => s?.read()?.connection?.id)
currentIndex: 0
}
visible: modelData.nmSettings.length > 1
}
Button {
text: "Connect"
onClicked: {
if (chosenSettings)
modelData.connectWithSettings(chosenSettings);
else
modelData.connect();
}
visible: !modelData.connected
}
Button {
text: "Disconnect"
onClicked: modelData.disconnect()
visible: modelData.connected
}
Button {
text: "Forget"
onClicked: modelData.forget()
visible: modelData.known
}
Button {
text: "Edit"
visible: modelData.known
onClicked: {
if (chosenSettings)
editorComponent.createObject(null, {
nmSettings: chosenSettings
});
}
}
}
Loader {
id: failLoader
Layout.alignment: Qt.AlignRight
visible: sourceComponent !== null
}
}
}
}
}
delegate: Component {
Loader {
required property var modelData
width: ListView.view.width
sourceComponent: deviceDelegate
}
}
}

View file

@ -19,7 +19,8 @@ namespace {
QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg);
}
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
WifiNetwork::WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent)
: Network(std::move(ssid), device, parent) {};
void WifiNetwork::connectWithPsk(const QString& psk) {
if (this->bConnected) {
@ -42,9 +43,6 @@ void WifiDevice::setScannerEnabled(bool enabled) {
this->bScannerEnabled = enabled;
}
void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); }
void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) {

View file

@ -7,7 +7,6 @@
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
@ -27,7 +26,7 @@ class WifiNetwork: public Network {
// clang-format on
public:
explicit WifiNetwork(QString ssid, QObject* parent = nullptr);
explicit WifiNetwork(QString ssid, NetworkDevice* device, QObject* parent = nullptr);
/// Attempt to connect to the network with the given PSK. If the PSK is wrong,
/// a @@Network.connectionFailed(s) signal will be emitted with `NoSecrets`.
///
@ -61,9 +60,6 @@ class WifiDevice: public NetworkDevice {
QML_UNCREATABLE("");
// clang-format off
/// A list of this available or connected wifi networks.
QSDOC_TYPE_OVERRIDE(ObjectModel<WifiNetwork>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// True when currently scanning for networks.
/// When enabled, the scanner populates the device with an active list of available wifi networks.
Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled);
@ -74,10 +70,6 @@ class WifiDevice: public NetworkDevice {
public:
explicit WifiDevice(QObject* parent = nullptr);
void networkAdded(WifiNetwork* net);
void networkRemoved(WifiNetwork* net);
[[nodiscard]] ObjectModel<WifiNetwork>* networks() { return &this->mNetworks; }
QBindable<bool> bindableScannerEnabled() { return &this->bScannerEnabled; }
[[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; }
void setScannerEnabled(bool enabled);
@ -88,12 +80,11 @@ signals:
void scannerEnabledChanged(bool enabled);
private:
ObjectModel<WifiNetwork> mNetworks {this};
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged);
};
}; // namespace qs::network
} // namespace qs::network
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network);
QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device);

22
src/network/wired.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "wired.hpp"
#include <qobject.h>
#include "device.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
WiredDevice::WiredDevice(QObject* parent): NetworkDevice(DeviceType::Wired, parent) {}
void WiredDevice::networkAdded(Network* net) {
this->NetworkDevice::networkAdded(net);
this->bNetwork = net;
};
void WiredDevice::networkRemoved(Network* net) {
this->NetworkDevice::networkRemoved(net);
this->bNetwork = nullptr;
};
} // namespace qs::network

53
src/network/wired.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "device.hpp"
#include "network.hpp"
namespace qs::network {
///! Wired variant of a @@NetworkDevice.
class WiredDevice: public NetworkDevice {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("");
/// The wired network for this device or `null`.
///
/// > [!NOTE] This network is only available when @@hasLink is `true`.
Q_PROPERTY(Network* network READ network NOTIFY networkChanged BINDABLE bindableNetwork);
/// The maximum speed of the physical device link, in megabits per second.
Q_PROPERTY(quint32 linkSpeed READ default NOTIFY linkSpeedChanged BINDABLE bindableLinkSpeed);
/// True if the wired device has a physical link (cable plugged in).
Q_PROPERTY(bool hasLink READ default NOTIFY hasLinkChanged BINDABLE bindableHasLink);
public:
explicit WiredDevice(QObject* parent = nullptr);
void networkAdded(Network* net) override;
void networkRemoved(Network* net) override;
[[nodiscard]] Network* network() const { return this->bNetwork; }
QBindable<Network*> bindableNetwork() { return &this->bNetwork; }
QBindable<quint32> bindableLinkSpeed() { return &this->bLinkSpeed; }
QBindable<bool> bindableHasLink() { return &this->bHasLink; }
signals:
void networkChanged();
void linkSpeedChanged();
void hasLinkChanged();
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, Network*, bNetwork, &WiredDevice::networkChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, quint32, bLinkSpeed, &WiredDevice::linkSpeedChanged);
Q_OBJECT_BINDABLE_PROPERTY(WiredDevice, bool, bHasLink, &WiredDevice::hasLinkChanged);
// clang-format on
};
} // namespace qs::network