mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-02-23 03:33:57 +11:00
networking: add networking library
This commit is contained in:
parent
bcc3d4265e
commit
db37dc580a
34 changed files with 3177 additions and 1 deletions
|
|
@ -33,3 +33,7 @@ add_subdirectory(services)
|
|||
if (BLUETOOTH)
|
||||
add_subdirectory(bluetooth)
|
||||
endif()
|
||||
|
||||
if (NETWORK)
|
||||
add_subdirectory(network)
|
||||
endif()
|
||||
|
|
|
|||
24
src/network/CMakeLists.txt
Normal file
24
src/network/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
add_subdirectory(nm)
|
||||
|
||||
qt_add_library(quickshell-network STATIC
|
||||
network.cpp
|
||||
device.cpp
|
||||
wifi.cpp
|
||||
)
|
||||
|
||||
target_include_directories(quickshell-network PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-network
|
||||
URI Quickshell.Networking
|
||||
VERSION 0.1
|
||||
DEPENDENCIES QtQml
|
||||
)
|
||||
|
||||
qs_add_module_deps_light(quickshell-network Quickshell)
|
||||
install_qml_module(quickshell-network)
|
||||
target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus)
|
||||
qs_add_link_dependencies(quickshell-network quickshell-dbus)
|
||||
target_link_libraries(quickshell PRIVATE quickshell-networkplugin)
|
||||
qs_module_pch(quickshell-network SET dbus)
|
||||
82
src/network/device.cpp
Normal file
82
src/network/device.cpp
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#include "device.hpp"
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) {
|
||||
switch (state) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Connecting: return QStringLiteral("Connecting");
|
||||
case Connected: return QStringLiteral("Connected");
|
||||
case Disconnecting: return QStringLiteral("Disconnecting");
|
||||
case Disconnected: return QStringLiteral("Disconnected");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString DeviceType::toString(DeviceType::Enum type) {
|
||||
switch (type) {
|
||||
case None: return QStringLiteral("None");
|
||||
case Wifi: return QStringLiteral("Wifi");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString NMDeviceState::toString(NMDeviceState::Enum state) {
|
||||
switch (state) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Unmanaged: return QStringLiteral("Not managed by NetworkManager");
|
||||
case Unavailable: return QStringLiteral("Unavailable");
|
||||
case Disconnected: return QStringLiteral("Disconnected");
|
||||
case Prepare: return QStringLiteral("Preparing to connect");
|
||||
case Config: return QStringLiteral("Connecting to a network");
|
||||
case NeedAuth: return QStringLiteral("Waiting for authentication");
|
||||
case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network");
|
||||
case IPCheck:
|
||||
return QStringLiteral("Checking if further action is required for the requested connection");
|
||||
case Secondaries:
|
||||
return QStringLiteral("Waiting for a required secondary connection to activate");
|
||||
case Activated: return QStringLiteral("Connected");
|
||||
case Deactivating: return QStringLiteral("Disconnecting");
|
||||
case Failed: return QStringLiteral("Failed to connect");
|
||||
default: return QStringLiteral("Unknown");
|
||||
};
|
||||
}
|
||||
|
||||
NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) {
|
||||
this->bindableConnected().setBinding([this]() {
|
||||
return this->bState == DeviceConnectionState::Connected;
|
||||
});
|
||||
};
|
||||
|
||||
void NetworkDevice::setAutoconnect(bool autoconnect) {
|
||||
if (this->bAutoconnect == autoconnect) return;
|
||||
emit this->requestSetAutoconnect(autoconnect);
|
||||
}
|
||||
|
||||
void NetworkDevice::disconnect() {
|
||||
if (this->bState == DeviceConnectionState::Disconnected) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
|
||||
return;
|
||||
}
|
||||
if (this->bState == DeviceConnectionState::Disconnecting) {
|
||||
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
|
||||
return;
|
||||
}
|
||||
qCDebug(logNetworkDevice) << "Disconnecting from device" << this;
|
||||
this->requestDisconnect();
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
133
src/network/device.hpp
Normal file
133
src/network/device.hpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! Connection state of a NetworkDevice.
|
||||
class DeviceConnectionState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
Disconnecting = 3,
|
||||
Disconnected = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state);
|
||||
};
|
||||
|
||||
///! Type of network device.
|
||||
class DeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Wifi = 1,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(DeviceType::Enum type);
|
||||
};
|
||||
|
||||
///! NetworkManager-specific device state.
|
||||
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
|
||||
class NMDeviceState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Unmanaged = 10,
|
||||
Unavailable = 20,
|
||||
Disconnected = 30,
|
||||
Prepare = 40,
|
||||
Config = 50,
|
||||
NeedAuth = 60,
|
||||
IPConfig = 70,
|
||||
IPCheck = 80,
|
||||
Secondaries = 90,
|
||||
Activated = 100,
|
||||
Deactivating = 110,
|
||||
Failed = 120,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NMDeviceState::Enum state);
|
||||
};
|
||||
|
||||
///! A network device.
|
||||
/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points.
|
||||
class NetworkDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("Devices can only be acquired through Network");
|
||||
// clang-format off
|
||||
/// The device type.
|
||||
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);
|
||||
/// 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.
|
||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||
/// Connection state of the device.
|
||||
Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||
/// A more specific device state when the backend is NetworkManager.
|
||||
Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState);
|
||||
/// True if the device is allowed to autoconnect.
|
||||
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit NetworkDevice(DeviceType::Enum type, QObject* parent = nullptr);
|
||||
|
||||
/// Disconnects the device and prevents it from automatically activating further connections.
|
||||
Q_INVOKABLE void disconnect();
|
||||
|
||||
[[nodiscard]] DeviceType::Enum type() const { return this->mType; };
|
||||
QBindable<QString> bindableName() { return &this->bName; };
|
||||
[[nodiscard]] QString name() const { return this->bName; };
|
||||
QBindable<QString> bindableAddress() { return &this->bAddress; };
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; };
|
||||
QBindable<DeviceConnectionState::Enum> bindableState() { return &this->bState; };
|
||||
QBindable<NMDeviceState::Enum> bindableNmState() { return &this->bNmState; };
|
||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
||||
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; };
|
||||
void setAutoconnect(bool autoconnect);
|
||||
|
||||
signals:
|
||||
void requestDisconnect();
|
||||
void requestSetAutoconnect(bool autoconnect);
|
||||
void nameChanged();
|
||||
void addressChanged();
|
||||
void connectedChanged();
|
||||
void stateChanged();
|
||||
void nmStateChanged();
|
||||
void autoconnectChanged();
|
||||
|
||||
private:
|
||||
DeviceType::Enum mType;
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
13
src/network/module.md
Normal file
13
src/network/module.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name = "Quickshell.Networking"
|
||||
description = "Network API"
|
||||
headers = [
|
||||
"network.hpp",
|
||||
"device.hpp",
|
||||
"wifi.hpp",
|
||||
]
|
||||
-----
|
||||
This module exposes Network management APIs provided by a supported network backend.
|
||||
For now, the only backend available is the NetworkManager DBus interface.
|
||||
Both DBus and NetworkManager must be running to use it.
|
||||
|
||||
See the @@Quickshell.Networking.Networking singleton.
|
||||
65
src/network/network.cpp
Normal file
65
src/network/network.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "network.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstringliteral.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
#include "device.hpp"
|
||||
#include "nm/backend.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
QString NetworkState::toString(NetworkState::Enum state) {
|
||||
switch (state) {
|
||||
case NetworkState::Connecting: return QStringLiteral("Connecting");
|
||||
case NetworkState::Connected: return QStringLiteral("Connected");
|
||||
case NetworkState::Disconnecting: return QStringLiteral("Disconnecting");
|
||||
case NetworkState::Disconnected: return QStringLiteral("Disconnected");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
Networking::Networking(QObject* parent): QObject(parent) {
|
||||
// Try to create the NetworkManager backend and bind to it.
|
||||
auto* nm = new NetworkManager(this);
|
||||
if (nm->isAvailable()) {
|
||||
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
|
||||
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
|
||||
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
|
||||
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
|
||||
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
|
||||
|
||||
this->mBackend = nm;
|
||||
this->mBackendType = NetworkBackendType::NetworkManager;
|
||||
return;
|
||||
} else {
|
||||
delete nm;
|
||||
}
|
||||
|
||||
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
|
||||
}
|
||||
|
||||
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
|
||||
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
|
||||
|
||||
void Networking::setWifiEnabled(bool enabled) {
|
||||
if (this->bWifiEnabled == enabled) return;
|
||||
emit this->requestSetWifiEnabled(enabled);
|
||||
}
|
||||
|
||||
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
|
||||
this->bStateChanging.setBinding([this] {
|
||||
auto state = this->bState.value();
|
||||
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
|
||||
});
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
142
src/network/network.hpp
Normal file
142
src/network/network.hpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/model.hpp"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! The connection state of a Network.
|
||||
class NetworkState: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
Disconnecting = 3,
|
||||
Disconnected = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NetworkState::Enum state);
|
||||
};
|
||||
|
||||
///! The backend supplying the Network service.
|
||||
class NetworkBackendType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
NetworkManager = 1,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
class NetworkBackend: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
[[nodiscard]] virtual bool isAvailable() const = 0;
|
||||
|
||||
protected:
|
||||
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
|
||||
};
|
||||
|
||||
///! 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 Networking: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_SINGLETON;
|
||||
QML_ELEMENT;
|
||||
// clang-format off
|
||||
/// A list of all network 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);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit Networking(QObject* parent = nullptr);
|
||||
|
||||
[[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; };
|
||||
|
||||
signals:
|
||||
void requestSetWifiEnabled(bool enabled);
|
||||
void wifiEnabledChanged();
|
||||
void wifiHardwareEnabledChanged();
|
||||
|
||||
private slots:
|
||||
void deviceAdded(NetworkDevice* dev);
|
||||
void deviceRemoved(NetworkDevice* dev);
|
||||
|
||||
private:
|
||||
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);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
///! A network.
|
||||
class Network: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
|
||||
|
||||
// clang-format off
|
||||
/// The name of the network.
|
||||
Q_PROPERTY(QString name READ name CONSTANT);
|
||||
/// True if the network is connected.
|
||||
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
|
||||
/// The connectivity state of the network.
|
||||
Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
|
||||
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
|
||||
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit Network(QString name, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QString name() const { return this->mName; };
|
||||
QBindable<bool> bindableConnected() { return &this->bConnected; }
|
||||
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
|
||||
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
|
||||
|
||||
signals:
|
||||
void connectedChanged();
|
||||
void stateChanged();
|
||||
void stateChangingChanged();
|
||||
|
||||
protected:
|
||||
QString mName;
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
79
src/network/nm/CMakeLists.txt
Normal file
79
src/network/nm/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES
|
||||
CLASSNAME DBusNetworkManagerProxy
|
||||
NO_NAMESPACE TRUE
|
||||
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.xml
|
||||
dbus_nm_backend
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES
|
||||
CLASSNAME DBusNMDeviceProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Device.xml
|
||||
dbus_nm_device
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES
|
||||
CLASSNAME DBusNMWirelessProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Device.Wireless.xml
|
||||
dbus_nm_wireless
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
|
||||
CLASSNAME DBusNMAccessPointProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.AccessPoint.xml
|
||||
dbus_nm_accesspoint
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES
|
||||
CLASSNAME DBusNMConnectionSettingsProxy
|
||||
NO_NAMESPACE TRUE
|
||||
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Settings.Connection.xml
|
||||
dbus_nm_connection_settings
|
||||
)
|
||||
|
||||
set_source_files_properties(org.freedesktop.NetworkManager.Connection.Active.xml PROPERTIES
|
||||
CLASSNAME DBusNMActiveConnectionProxy
|
||||
NO_NAMESPACE TRUE
|
||||
)
|
||||
|
||||
qt_add_dbus_interface(NM_DBUS_INTERFACES
|
||||
org.freedesktop.NetworkManager.Connection.Active.xml
|
||||
dbus_nm_active_connection
|
||||
)
|
||||
|
||||
qt_add_library(quickshell-network-nm STATIC
|
||||
backend.cpp
|
||||
device.cpp
|
||||
connection.cpp
|
||||
accesspoint.cpp
|
||||
wireless.cpp
|
||||
utils.cpp
|
||||
enums.hpp
|
||||
${NM_DBUS_INTERFACES}
|
||||
)
|
||||
|
||||
target_include_directories(quickshell-network-nm PUBLIC
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
|
||||
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
|
||||
71
src/network/nm/accesspoint.cpp
Normal file
71
src/network/nm/accesspoint.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#include "accesspoint.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "dbus_nm_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMAccessPoint::NMAccessPoint(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->proxy = new DBusNMAccessPointProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for access point at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
&this->accessPointProperties,
|
||||
&DBusPropertyGroup::getAllFinished,
|
||||
this,
|
||||
&NMAccessPoint::loaded,
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
|
||||
this->accessPointProperties.setInterface(this->proxy);
|
||||
this->accessPointProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
bool NMAccessPoint::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMAccessPoint::address() const { return this->proxy ? this->proxy->service() : QString(); }
|
||||
QString NMAccessPoint::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NM80211ApFlags::Enum>
|
||||
DBusDataTransform<qs::network::NM80211ApFlags::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211ApFlags::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<qs::network::NM80211ApSecurityFlags::Enum>
|
||||
DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211ApSecurityFlags::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<qs::network::NM80211Mode::Enum>
|
||||
DBusDataTransform<qs::network::NM80211Mode::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NM80211Mode::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
92
src/network/nm/accesspoint.hpp
Normal file
92
src/network/nm/accesspoint.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_accesspoint.h"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211ApFlags::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211ApFlags::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211ApSecurityFlags::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NM80211Mode::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NM80211Mode::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
/// Proxy of a /org/freedesktop/NetworkManager/AccessPoint/* object.
|
||||
class NMAccessPoint: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMAccessPoint(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QByteArray ssid() const { return this->bSsid; };
|
||||
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
|
||||
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; };
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; };
|
||||
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; };
|
||||
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; };
|
||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void ssidChanged(const QByteArray& ssid);
|
||||
void signalStrengthChanged(quint8 signal);
|
||||
void flagsChanged(NM80211ApFlags::Enum flags);
|
||||
void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags);
|
||||
void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags);
|
||||
void modeChanged(NM80211Mode::Enum mode);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, QByteArray, bSsid, &NMAccessPoint::ssidChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, quint8, bSignalStrength, &NMAccessPoint::signalStrengthChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApFlags::Enum, bFlags, &NMAccessPoint::flagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPoint::wpaFlagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPoint::rsnFlagsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211Mode::Enum, bMode, &NMAccessPoint::modeChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, WifiSecurityType::Enum, bSecurity, &NMAccessPoint::securityChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSsid, bSsid, accessPointProperties, "Ssid");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSignalStrength, bSignalStrength, accessPointProperties, "Strength");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pFlags, bFlags, accessPointProperties, "Flags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags");
|
||||
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pMode, bMode, accessPointProperties, "Mode");
|
||||
// clang-format on
|
||||
|
||||
DBusNMAccessPointProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
270
src/network/nm/backend.cpp
Normal file
270
src/network/nm/backend.cpp
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
#include "backend.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "dbus_nm_device.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "wireless.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
|
||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
||||
|
||||
auto bus = QDBusConnection::systemBus();
|
||||
if (!bus.isConnected()) {
|
||||
qCWarning(
|
||||
logNetworkManager
|
||||
) << "Could not connect to DBus. NetworkManager backend will not work.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->proxy = new DBusNetworkManagerProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
"/org/freedesktop/NetworkManager",
|
||||
bus,
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCDebug(
|
||||
logNetworkManager
|
||||
) << "NetworkManager is not currently running. This network backend will not work";
|
||||
} else {
|
||||
this->init();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::init() {
|
||||
// clang-format off
|
||||
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded);
|
||||
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved);
|
||||
// clang-format on
|
||||
|
||||
this->dbusProperties.setInterface(this->proxy);
|
||||
this->dbusProperties.updateAllViaGetAll();
|
||||
|
||||
this->registerDevices();
|
||||
}
|
||||
|
||||
void NetworkManager::registerDevices() {
|
||||
auto pending = this->proxy->GetAllDevices();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message();
|
||||
} else {
|
||||
for (const QDBusObjectPath& devicePath: reply.value()) {
|
||||
this->registerDevice(devicePath.path());
|
||||
}
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::registerDevice(const QString& path) {
|
||||
if (this->mDevices.contains(path)) {
|
||||
qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* temp = new DBusNMDeviceProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
auto callback = [this, path, temp](uint value, const QDBusError& error) {
|
||||
if (error.isValid()) {
|
||||
qCWarning(logNetworkManager) << "Failed to get device type:" << error;
|
||||
} else {
|
||||
auto type = static_cast<qs::network::NMDeviceType::Enum>(value);
|
||||
NMDevice* dev = nullptr;
|
||||
this->mDevices.insert(path, nullptr);
|
||||
|
||||
switch (type) {
|
||||
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
if (!dev->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete dev;
|
||||
} else {
|
||||
this->mDevices[path] = dev;
|
||||
// Only register a frontend device while it's managed by NM.
|
||||
auto onManagedChanged = [this, dev, type](bool managed) {
|
||||
managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev);
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
|
||||
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
|
||||
QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged);
|
||||
// clang-format on
|
||||
|
||||
if (dev->managed()) this->registerFrontendDevice(type, dev);
|
||||
}
|
||||
}
|
||||
temp->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
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 DeviceConnectionState::Unknown;
|
||||
case 30: return DeviceConnectionState::Disconnected;
|
||||
case 40 ... 90: return DeviceConnectionState::Connecting;
|
||||
case 100: return DeviceConnectionState::Connected;
|
||||
case 110 ... 120: return DeviceConnectionState::Disconnecting;
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
|
||||
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
|
||||
frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); });
|
||||
frontendDev->bindableState().setBinding(translateState);
|
||||
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
|
||||
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
|
||||
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
|
||||
// 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());
|
||||
}
|
||||
|
||||
void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
|
||||
auto iter = this->mDevices.find(path.path());
|
||||
if (iter == this->mDevices.end()) {
|
||||
qCWarning(logNetworkManager) << "Sent removal signal for" << path.path()
|
||||
<< "which is not registered.";
|
||||
} else {
|
||||
auto* dev = iter.value();
|
||||
this->mDevices.erase(iter);
|
||||
if (dev) {
|
||||
this->removeFrontendDevice(dev);
|
||||
delete dev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::activateConnection(
|
||||
const QDBusObjectPath& connPath,
|
||||
const QDBusObjectPath& devPath
|
||||
) {
|
||||
auto pending = this->proxy->ActivateConnection(connPath, devPath, QDBusObjectPath("/"));
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QDBusObjectPath> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager) << "Failed to activate connection:" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
) {
|
||||
auto pending = this->proxy->AddAndActivateConnection(settings, devPath, specificObjectPath);
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to add and activate connection:" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NetworkManager::setWifiEnabled(bool enabled) {
|
||||
if (enabled == this->bWifiEnabled) return;
|
||||
this->bWifiEnabled = enabled;
|
||||
this->pWifiEnabled.write();
|
||||
}
|
||||
|
||||
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
|
||||
|
||||
} // namespace qs::network
|
||||
67
src/network/nm/backend.hpp
Normal file
67
src/network/nm/backend.hpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "dbus_nm_backend.h"
|
||||
#include "device.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
class NetworkManager: public NetworkBackend {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NetworkManager(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isAvailable() const override;
|
||||
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
|
||||
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; };
|
||||
|
||||
signals:
|
||||
void deviceAdded(NetworkDevice* device);
|
||||
void deviceRemoved(NetworkDevice* device);
|
||||
void wifiEnabledChanged(bool enabled);
|
||||
void wifiHardwareEnabledChanged(bool enabled);
|
||||
|
||||
public slots:
|
||||
void setWifiEnabled(bool enabled);
|
||||
|
||||
private slots:
|
||||
void onDevicePathAdded(const QDBusObjectPath& path);
|
||||
void onDevicePathRemoved(const QDBusObjectPath& path);
|
||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||
void addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& specificObjectPath
|
||||
);
|
||||
|
||||
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);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
|
||||
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
|
||||
// clang-format on
|
||||
DBusNetworkManagerProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
151
src/network/nm/connection.cpp
Normal file
151
src/network/nm/connection.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#include "connection.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusmetatype.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_active_connection.h"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent): QObject(parent) {
|
||||
qDBusRegisterMetaType<ConnectionSettingsMap>();
|
||||
|
||||
this->proxy = new DBusNMConnectionSettingsProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
this->proxy,
|
||||
&DBusNMConnectionSettingsProxy::Updated,
|
||||
this,
|
||||
&NMConnectionSettings::updateSettings
|
||||
);
|
||||
this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); });
|
||||
|
||||
this->connectionSettingsProperties.setInterface(this->proxy);
|
||||
this->connectionSettingsProperties.updateAllViaGetAll();
|
||||
|
||||
this->updateSettings();
|
||||
}
|
||||
|
||||
void NMConnectionSettings::updateSettings() {
|
||||
auto pending = this->proxy->GetSettings();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<ConnectionSettingsMap> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to get" << this->path() << "settings:" << reply.error().message();
|
||||
} else {
|
||||
this->bSettings = reply.value();
|
||||
}
|
||||
|
||||
if (!this->mLoaded) {
|
||||
emit this->loaded();
|
||||
this->mLoaded = true;
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NMConnectionSettings::forget() {
|
||||
auto pending = this->proxy->Delete();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to forget" << this->path() << ":" << reply.error().message();
|
||||
}
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
bool NMConnectionSettings::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMConnectionSettings::address() const {
|
||||
return this->proxy ? this->proxy->service() : QString();
|
||||
}
|
||||
QString NMConnectionSettings::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->proxy = new DBusNMActiveConnectionProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->proxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection);
|
||||
QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged);
|
||||
// clang-format on
|
||||
|
||||
this->activeConnectionProperties.setInterface(this->proxy);
|
||||
this->activeConnectionProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) {
|
||||
auto enumReason = static_cast<NMConnectionStateReason::Enum>(reason);
|
||||
if (this->mStateReason == enumReason) return;
|
||||
this->mStateReason = enumReason;
|
||||
emit this->stateReasonChanged(enumReason);
|
||||
}
|
||||
|
||||
bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); }
|
||||
QString NMActiveConnection::address() const {
|
||||
return this->proxy ? this->proxy->service() : QString();
|
||||
}
|
||||
QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMConnectionState::Enum>
|
||||
DBusDataTransform<qs::network::NMConnectionState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMConnectionState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
105
src/network/nm/connection.hpp
Normal file
105
src/network/nm/connection.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_nm_active_connection.h"
|
||||
#include "dbus_nm_connection_settings.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMConnectionState::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
|
||||
class NMConnectionSettings: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
void forget();
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; };
|
||||
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
|
||||
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void settingsChanged(ConnectionSettingsMap settings);
|
||||
void securityChanged(WifiSecurityType::Enum security);
|
||||
void ssidChanged(QString ssid);
|
||||
|
||||
private:
|
||||
bool mLoaded = false;
|
||||
void updateSettings();
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged);
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties);
|
||||
// clang-format on
|
||||
|
||||
DBusNMConnectionSettingsProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object.
|
||||
class NMActiveConnection: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMActiveConnection(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; };
|
||||
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
|
||||
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; };
|
||||
|
||||
signals:
|
||||
void loaded();
|
||||
void connectionChanged(QDBusObjectPath path);
|
||||
void stateChanged(NMConnectionState::Enum state);
|
||||
void stateReasonChanged(NMConnectionStateReason::Enum reason);
|
||||
void uuidChanged(const QString& uuid);
|
||||
|
||||
private slots:
|
||||
void onStateChanged(quint32 state, quint32 reason);
|
||||
|
||||
private:
|
||||
NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection");
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pUuid, bUuid, activeConnectionProperties, "Uuid");
|
||||
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State");
|
||||
// clang-format on
|
||||
DBusNMActiveConnectionProxy* proxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
9
src/network/nm/dbus_types.hpp
Normal file
9
src/network/nm/dbus_types.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qmap.h>
|
||||
#include <qstring.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
|
||||
Q_DECLARE_METATYPE(ConnectionSettingsMap);
|
||||
143
src/network/nm/device.cpp
Normal file
143
src/network/nm/device.cpp
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#include "device.hpp"
|
||||
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qset.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../device.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
|
||||
}
|
||||
|
||||
NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
|
||||
this->deviceProxy = new DBusNMDeviceProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->deviceProxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
|
||||
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
|
||||
// clang-format on
|
||||
|
||||
this->deviceProperties.setInterface(this->deviceProxy);
|
||||
this->deviceProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
|
||||
const QString stringPath = path.path();
|
||||
|
||||
// Remove old active connection
|
||||
if (this->mActiveConnection) {
|
||||
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
|
||||
delete this->mActiveConnection;
|
||||
this->mActiveConnection = nullptr;
|
||||
}
|
||||
|
||||
// Create new active connection
|
||||
if (stringPath != "/") {
|
||||
auto* active = new NMActiveConnection(stringPath, this);
|
||||
if (!active->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
|
||||
delete active;
|
||||
} else {
|
||||
this->mActiveConnection = active;
|
||||
QObject::connect(
|
||||
active,
|
||||
&NMActiveConnection::loaded,
|
||||
this,
|
||||
[this, active]() { emit this->activeConnectionLoaded(active); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
|
||||
QSet<QString> newPathSet;
|
||||
for (const QDBusObjectPath& path: paths) {
|
||||
newPathSet.insert(path.path());
|
||||
}
|
||||
const auto existingPaths = this->mConnections.keys();
|
||||
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
|
||||
|
||||
const auto addedConnections = newPathSet - existingPathSet;
|
||||
const auto removedConnections = existingPathSet - newPathSet;
|
||||
|
||||
for (const QString& path: addedConnections) {
|
||||
this->registerConnection(path);
|
||||
}
|
||||
for (const QString& path: removedConnections) {
|
||||
auto* connection = this->mConnections.take(path);
|
||||
if (!connection) {
|
||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
|
||||
} else {
|
||||
delete connection;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void NMDevice::registerConnection(const QString& path) {
|
||||
auto* connection = new NMConnectionSettings(path, this);
|
||||
if (!connection->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete connection;
|
||||
} else {
|
||||
this->mConnections.insert(path, connection);
|
||||
QObject::connect(
|
||||
connection,
|
||||
&NMConnectionSettings::loaded,
|
||||
this,
|
||||
[this, connection]() { emit this->connectionLoaded(connection); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void NMDevice::disconnect() { this->deviceProxy->Disconnect(); }
|
||||
|
||||
void NMDevice::setAutoconnect(bool autoconnect) {
|
||||
if (autoconnect == this->bAutoconnect) return;
|
||||
this->bAutoconnect = autoconnect;
|
||||
this->pAutoconnect.write();
|
||||
}
|
||||
|
||||
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
|
||||
QString NMDevice::address() const {
|
||||
return this->deviceProxy ? this->deviceProxy->service() : QString();
|
||||
}
|
||||
QString NMDevice::path() const { return this->deviceProxy ? this->deviceProxy->path() : QString(); }
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMDeviceState::Enum>
|
||||
DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
100
src/network/nm/device.hpp
Normal file
100
src/network/nm/device.hpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_device.h"
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMDeviceState::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
|
||||
// Only the members from the org.freedesktop.NetworkManager.Device interface.
|
||||
// Owns the lifetime of NMActiveConnection(s) and NMConnectionSetting(s).
|
||||
class NMDevice: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMDevice(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] virtual bool isValid() const;
|
||||
[[nodiscard]] QString path() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QString interface() const { return this->bInterface; };
|
||||
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; };
|
||||
[[nodiscard]] bool managed() const { return this->bManaged; };
|
||||
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; };
|
||||
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
|
||||
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; };
|
||||
|
||||
signals:
|
||||
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
|
||||
void addAndActivateConnection(
|
||||
const ConnectionSettingsMap& settings,
|
||||
const QDBusObjectPath& devPath,
|
||||
const QDBusObjectPath& apPath
|
||||
);
|
||||
void connectionLoaded(NMConnectionSettings* connection);
|
||||
void connectionRemoved(NMConnectionSettings* connection);
|
||||
void availableConnectionPathsChanged(QList<QDBusObjectPath> paths);
|
||||
void activeConnectionPathChanged(const QDBusObjectPath& connection);
|
||||
void activeConnectionLoaded(NMActiveConnection* active);
|
||||
void interfaceChanged(const QString& interface);
|
||||
void hwAddressChanged(const QString& hwAddress);
|
||||
void managedChanged(bool managed);
|
||||
void stateChanged(NMDeviceState::Enum state);
|
||||
void autoconnectChanged(bool autoconnect);
|
||||
|
||||
public slots:
|
||||
void disconnect();
|
||||
void setAutoconnect(bool autoconnect);
|
||||
|
||||
private slots:
|
||||
void onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
|
||||
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
|
||||
|
||||
private:
|
||||
void registerConnection(const QString& path);
|
||||
|
||||
QHash<QString, NMConnectionSettings*> mConnections;
|
||||
NMActiveConnection* mActiveConnection = nullptr;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bHwAddress, deviceProperties, "HwAddress");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed");
|
||||
QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State");
|
||||
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");
|
||||
// clang-format on
|
||||
|
||||
DBusNMDeviceProxy* deviceProxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
156
src/network/nm/enums.hpp
Normal file
156
src/network/nm/enums.hpp
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
// Indicates the type of hardware represented by a device object.
|
||||
class NMDeviceType: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Ethernet = 1,
|
||||
Wifi = 2,
|
||||
Unused1 = 3,
|
||||
Unused2 = 4,
|
||||
Bluetooth = 5,
|
||||
OlpcMesh = 6,
|
||||
Wimax = 7,
|
||||
Modem = 8,
|
||||
InfiniBand = 9,
|
||||
Bond = 10,
|
||||
Vlan = 11,
|
||||
Adsl = 12,
|
||||
Bridge = 13,
|
||||
Generic = 14,
|
||||
Team = 15,
|
||||
Tun = 16,
|
||||
IpTunnel = 17,
|
||||
MacVlan = 18,
|
||||
VxLan = 19,
|
||||
Veth = 20,
|
||||
MacSec = 21,
|
||||
Dummy = 22,
|
||||
Ppp = 23,
|
||||
OvsInterface = 24,
|
||||
OvsPort = 25,
|
||||
OvsBridge = 26,
|
||||
Wpan = 27,
|
||||
Lowpan = 28,
|
||||
Wireguard = 29,
|
||||
WifiP2P = 30,
|
||||
Vrf = 31,
|
||||
Loopback = 32,
|
||||
Hsr = 33,
|
||||
IpVlan = 34,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 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 {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint16 {
|
||||
None = 0,
|
||||
CipherWep40 = 1,
|
||||
CipherWep104 = 2,
|
||||
CipherTkip = 4,
|
||||
CipherCcmp = 8,
|
||||
Wpa = 16,
|
||||
Rsn = 32,
|
||||
Ap = 64,
|
||||
Adhoc = 128,
|
||||
FreqValid = 256,
|
||||
Freq2Ghz = 512,
|
||||
Freq5Ghz = 1024,
|
||||
Freq6Ghz = 2048,
|
||||
Mesh = 4096,
|
||||
IbssRsn = 8192,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Indicates the 802.11 mode an access point is currently in.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211Mode.
|
||||
class NM80211Mode: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Adhoc = 1,
|
||||
Infra = 2,
|
||||
Ap = 3,
|
||||
Mesh = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 802.11 access point flags.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
|
||||
class NM80211ApFlags: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
None = 0,
|
||||
Privacy = 1,
|
||||
Wps = 2,
|
||||
WpsPbc = 4,
|
||||
WpsPin = 8,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// 802.11 access point security and authentication flags.
|
||||
// These flags describe the current system requirements of an access point as determined from the access point's beacon.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
|
||||
class NM80211ApSecurityFlags: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint16 {
|
||||
None = 0,
|
||||
PairWep40 = 1,
|
||||
PairWep104 = 2,
|
||||
PairTkip = 4,
|
||||
PairCcmp = 8,
|
||||
GroupWep40 = 16,
|
||||
GroupWep104 = 32,
|
||||
GroupTkip = 64,
|
||||
GroupCcmp = 128,
|
||||
KeyMgmtPsk = 256,
|
||||
KeyMgmt8021x = 512,
|
||||
KeyMgmtSae = 1024,
|
||||
KeyMgmtOwe = 2048,
|
||||
KeyMgmtOweTm = 4096,
|
||||
KeyMgmtEapSuiteB192 = 8192,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
// Indicates the state of a connection to a specific network while it is starting, connected, or disconnected from that network.
|
||||
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionState.
|
||||
class NMConnectionState: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
Activating = 1,
|
||||
Activated = 2,
|
||||
Deactivating = 3,
|
||||
Deactivated = 4
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.AccessPoint">
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Connection.Active">
|
||||
<signal name="StateChanged">
|
||||
<arg name="state" type="u"/>
|
||||
<arg name="reason" type="u"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device.Wireless">
|
||||
<method name="GetAllAccessPoints" type="ao"/>
|
||||
<method name="RequestScan">
|
||||
<arg name="options" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QVariantMap"/>
|
||||
</method>
|
||||
<signal name="AccessPointAdded">
|
||||
<arg name="access_point" type="o"/>
|
||||
</signal>
|
||||
<signal name="AccessPointRemoved">
|
||||
<arg name="access_point" type="o"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
5
src/network/nm/org.freedesktop.NetworkManager.Device.xml
Normal file
5
src/network/nm/org.freedesktop.NetworkManager.Device.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Device">
|
||||
<method name="Disconnect"/>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager.Settings.Connection">
|
||||
<method name="GetSettings">
|
||||
<arg name="settings" type="a{sa{sv}}" direction="out"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ConnectionSettingsMap"/>
|
||||
</method>
|
||||
<method name="Delete"/>
|
||||
<signal name="Updated"/>
|
||||
<signal name="Removed"/>
|
||||
</interface>
|
||||
</node>
|
||||
27
src/network/nm/org.freedesktop.NetworkManager.xml
Normal file
27
src/network/nm/org.freedesktop.NetworkManager.xml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<node>
|
||||
<interface name="org.freedesktop.NetworkManager">
|
||||
<method name="GetAllDevices">
|
||||
<arg direction="out" type="ao" name="devices"/>
|
||||
</method>
|
||||
<method name="ActivateConnection">
|
||||
<arg direction="in" type="o" name="connection"/>
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="o" name="specific_object"/>
|
||||
<arg direction="out" type="o" name="active_connection"/>
|
||||
</method>
|
||||
<method name="AddAndActivateConnection">
|
||||
<arg direction="in" type="a{sa{sv}}" name="connection"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="ConnectionSettingsMap"/>
|
||||
<arg direction="in" type="o" name="device"/>
|
||||
<arg direction="in" type="o" name="specific_object"/>
|
||||
<arg direction="out" type="o" name="path"/>
|
||||
<arg direction="out" type="o" name="active_connection"/>
|
||||
</method>
|
||||
<signal name="DeviceAdded">
|
||||
<arg type="o" name="device_path"/>
|
||||
</signal>
|
||||
<signal name="DeviceRemoved">
|
||||
<arg type="o" name="device_path"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
248
src/network/nm/utils.cpp
Normal file
248
src/network/nm/utils.cpp
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
#include "utils.hpp"
|
||||
|
||||
// We depend on non-std Linux extensions that ctime doesn't put in the global namespace
|
||||
// NOLINTNEXTLINE(modernize-deprecated-headers)
|
||||
#include <time.h>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatetime.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) {
|
||||
const QVariantMap& security = settings.value("802-11-wireless-security");
|
||||
if (security.isEmpty()) {
|
||||
return WifiSecurityType::Open;
|
||||
};
|
||||
|
||||
const QString keyMgmt = security["key-mgmt"].toString();
|
||||
const QString authAlg = security["auth-alg"].toString();
|
||||
const QList<QVariant> proto = security["proto"].toList();
|
||||
|
||||
if (keyMgmt == "none") {
|
||||
return WifiSecurityType::StaticWep;
|
||||
} else if (keyMgmt == "ieee8021x") {
|
||||
if (authAlg == "leap") {
|
||||
return WifiSecurityType::Leap;
|
||||
} else {
|
||||
return WifiSecurityType::DynamicWep;
|
||||
}
|
||||
} else if (keyMgmt == "wpa-psk") {
|
||||
if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaPsk;
|
||||
return WifiSecurityType::Wpa2Psk;
|
||||
} else if (keyMgmt == "wpa-eap") {
|
||||
if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaEap;
|
||||
return WifiSecurityType::Wpa2Eap;
|
||||
} else if (keyMgmt == "sae") {
|
||||
return WifiSecurityType::Sae;
|
||||
} else if (keyMgmt == "wpa-eap-suite-b-192") {
|
||||
return WifiSecurityType::Wpa3SuiteB192;
|
||||
}
|
||||
return WifiSecurityType::Open;
|
||||
}
|
||||
|
||||
bool deviceSupportsApCiphers(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
NM80211ApSecurityFlags::Enum apFlags,
|
||||
WifiSecurityType::Enum type
|
||||
) {
|
||||
bool havePair = false;
|
||||
bool haveGroup = false;
|
||||
// Device needs to support at least one pairwise and one group cipher
|
||||
|
||||
if (type == WifiSecurityType::StaticWep) {
|
||||
// Static WEP only uses group ciphers
|
||||
havePair = true;
|
||||
} else {
|
||||
if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::PairWep40) {
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::PairWep104)
|
||||
{
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::PairTkip) {
|
||||
havePair = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::PairCcmp) {
|
||||
havePair = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::GroupWep40) {
|
||||
haveGroup = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::GroupWep104)
|
||||
{
|
||||
haveGroup = true;
|
||||
}
|
||||
if (type != WifiSecurityType::StaticWep) {
|
||||
if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::GroupTkip) {
|
||||
haveGroup = true;
|
||||
}
|
||||
if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::GroupCcmp) {
|
||||
haveGroup = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (havePair && haveGroup);
|
||||
}
|
||||
|
||||
bool securityIsValid(
|
||||
WifiSecurityType::Enum type,
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adhoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
) {
|
||||
switch (type) {
|
||||
case WifiSecurityType::Open:
|
||||
if (apFlags & NM80211ApFlags::Privacy) return false;
|
||||
if (apWpa || apRsn) return false;
|
||||
break;
|
||||
case WifiSecurityType::Leap:
|
||||
if (adhoc) return false;
|
||||
case WifiSecurityType::StaticWep:
|
||||
if (!(apFlags & NM80211ApFlags::Privacy)) return false;
|
||||
if (apWpa || apRsn) {
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::StaticWep)) {
|
||||
if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::StaticWep)) return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::DynamicWep:
|
||||
if (adhoc) return false;
|
||||
if (apRsn || !(apFlags & NM80211ApFlags::Privacy)) return false;
|
||||
if (apWpa) {
|
||||
if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::DynamicWep)) return false;
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::WpaPsk:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Wpa)) return false;
|
||||
if (apWpa & NM80211ApSecurityFlags::KeyMgmtPsk) {
|
||||
if (apWpa & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apWpa & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::Wpa2Psk:
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (adhoc) {
|
||||
if (!(caps & NMWirelessCapabilities::IbssRsn)) return false;
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (apRsn & NM80211ApSecurityFlags::KeyMgmtPsk) {
|
||||
if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::WpaEap:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Wpa)) return false;
|
||||
if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::WpaEap)) return false;
|
||||
break;
|
||||
case WifiSecurityType::Wpa2Eap:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmt8021x)) return false;
|
||||
if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::Wpa2Eap)) return false;
|
||||
break;
|
||||
case WifiSecurityType::Sae:
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (adhoc) {
|
||||
if (!(caps & NMWirelessCapabilities::IbssRsn)) return false;
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (apRsn & NM80211ApSecurityFlags::KeyMgmtSae) {
|
||||
if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) {
|
||||
return true;
|
||||
}
|
||||
if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case WifiSecurityType::Owe:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtOwe)
|
||||
&& !(apRsn & NM80211ApSecurityFlags::KeyMgmtOweTm))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case WifiSecurityType::Wpa3SuiteB192:
|
||||
if (adhoc) return false;
|
||||
if (!(caps & NMWirelessCapabilities::Rsn)) return false;
|
||||
if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtEapSuiteB192)) return false;
|
||||
break;
|
||||
default: return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WifiSecurityType::Enum findBestWirelessSecurity(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adHoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
) {
|
||||
// Loop through security types from most to least secure since the enum
|
||||
// values are sequential and in priority order (0-10, excluding Unknown=11)
|
||||
for (int i = WifiSecurityType::Wpa3SuiteB192; i <= WifiSecurityType::Open; ++i) {
|
||||
auto type = static_cast<WifiSecurityType::Enum>(i);
|
||||
if (securityIsValid(type, caps, adHoc, apFlags, apWpa, apRsn)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return WifiSecurityType::Unknown;
|
||||
}
|
||||
|
||||
// NOLINTBEGIN
|
||||
QDateTime clockBootTimeToDateTime(qint64 clockBootTime) {
|
||||
clockid_t clkId = CLOCK_BOOTTIME;
|
||||
struct timespec tp {};
|
||||
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
int r = clock_gettime(clkId, &tp);
|
||||
if (r == -1 && errno == EINVAL) {
|
||||
clkId = CLOCK_MONOTONIC;
|
||||
r = clock_gettime(clkId, &tp);
|
||||
}
|
||||
|
||||
// Convert to milliseconds
|
||||
const qint64 nowInMs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
|
||||
|
||||
// Return a QDateTime of the millisecond diff
|
||||
const qint64 offset = clockBootTime - nowInMs;
|
||||
return QDateTime::fromMSecsSinceEpoch(now.toMSecsSinceEpoch() + offset);
|
||||
}
|
||||
// NOLINTEND
|
||||
|
||||
} // namespace qs::network
|
||||
45
src/network/nm/utils.hpp
Normal file
45
src/network/nm/utils.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdbusservicewatcher.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "dbus_types.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings);
|
||||
|
||||
bool deviceSupportsApCiphers(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
NM80211ApSecurityFlags::Enum apFlags,
|
||||
WifiSecurityType::Enum type
|
||||
);
|
||||
|
||||
// In sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid()
|
||||
// Given a set of device capabilities, and a desired security type to check
|
||||
// against, determines whether the combination of device, desired security type,
|
||||
// and AP capabilities intersect.
|
||||
bool securityIsValid(
|
||||
WifiSecurityType::Enum type,
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adhoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
);
|
||||
|
||||
WifiSecurityType::Enum findBestWirelessSecurity(
|
||||
NMWirelessCapabilities::Enum caps,
|
||||
bool adHoc,
|
||||
NM80211ApFlags::Enum apFlags,
|
||||
NM80211ApSecurityFlags::Enum apWpa,
|
||||
NM80211ApSecurityFlags::Enum apRsn
|
||||
);
|
||||
|
||||
QDateTime clockBootTimeToDateTime(qint64 clockBootTime);
|
||||
|
||||
} // namespace qs::network
|
||||
457
src/network/nm/wireless.cpp
Normal file
457
src/network/nm/wireless.cpp
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
#include "wireless.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdatetime.h>
|
||||
#include <qdbusconnection.h>
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qdbuspendingcall.h>
|
||||
#include <qdbuspendingreply.h>
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../../dbus/properties.hpp"
|
||||
#include "../network.hpp"
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_wireless.h"
|
||||
#include "dbus_types.hpp"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
using namespace qs::dbus;
|
||||
|
||||
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::updateReferenceConnection() {
|
||||
// If the network has no connections, the reference is nullptr.
|
||||
if (this->mConnections.isEmpty()) {
|
||||
this->mReferenceConn = nullptr;
|
||||
this->bSecurity = WifiSecurityType::Unknown;
|
||||
// Set security back to reference AP.
|
||||
if (this->mReferenceAp) {
|
||||
this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); });
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
// If the network has an active connection, use it as the reference.
|
||||
if (this->mActiveConnection) {
|
||||
auto* conn = this->mConnections.value(this->mActiveConnection->connection().path());
|
||||
if (conn && conn != this->mReferenceConn) {
|
||||
this->mReferenceConn = conn;
|
||||
this->bSecurity.setBinding([conn]() { return conn->security(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, choose the connection with the strongest security settings.
|
||||
NMConnectionSettings* selectedConn = nullptr;
|
||||
for (auto* conn: this->mConnections.values()) {
|
||||
if (!selectedConn || conn->security() > selectedConn->security()) {
|
||||
selectedConn = conn;
|
||||
}
|
||||
}
|
||||
if (this->mReferenceConn != selectedConn) {
|
||||
this->mReferenceConn = selectedConn;
|
||||
this->bSecurity.setBinding([selectedConn]() { return selectedConn->security(); });
|
||||
}
|
||||
}
|
||||
|
||||
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->mReferenceConn) {
|
||||
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->mConnections.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::addConnection(NMConnectionSettings* conn) {
|
||||
if (this->mConnections.contains(conn->path())) return;
|
||||
this->mConnections.insert(conn->path(), conn);
|
||||
auto onDestroyed = [this, conn]() {
|
||||
if (this->mConnections.take(conn->path())) {
|
||||
this->updateReferenceConnection();
|
||||
if (this->mConnections.isEmpty()) this->bKnown = false;
|
||||
if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared();
|
||||
}
|
||||
};
|
||||
// clang-format off
|
||||
QObject::connect(conn, &NMConnectionSettings::securityChanged, this, &NMWirelessNetwork::updateReferenceConnection);
|
||||
QObject::connect(conn, &NMConnectionSettings::destroyed, this, onDestroyed);
|
||||
// clang-format on
|
||||
this->bKnown = true;
|
||||
this->updateReferenceConnection();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) {
|
||||
if (this->mActiveConnection) return;
|
||||
this->mActiveConnection = active;
|
||||
this->bState.setBinding([active]() { return active->state(); });
|
||||
this->bReason.setBinding([active]() { return active->stateReason(); });
|
||||
auto onDestroyed = [this, active]() {
|
||||
if (this->mActiveConnection && this->mActiveConnection == active) {
|
||||
this->mActiveConnection = nullptr;
|
||||
this->updateReferenceConnection();
|
||||
this->bState = NMConnectionState::Deactivated;
|
||||
this->bReason = NMConnectionStateReason::None;
|
||||
}
|
||||
};
|
||||
QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed);
|
||||
this->updateReferenceConnection();
|
||||
};
|
||||
|
||||
void NMWirelessNetwork::forget() {
|
||||
if (this->mConnections.isEmpty()) return;
|
||||
for (auto* conn: this->mConnections.values()) {
|
||||
conn->forget();
|
||||
}
|
||||
}
|
||||
|
||||
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
|
||||
: NMDevice(path, parent)
|
||||
, mScanTimer(this) {
|
||||
this->wirelessProxy = new DBusNMWirelessProxy(
|
||||
"org.freedesktop.NetworkManager",
|
||||
path,
|
||||
QDBusConnection::systemBus(),
|
||||
this
|
||||
);
|
||||
|
||||
if (!this->wirelessProxy->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Cannot create DBus interface for wireless device at" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(
|
||||
&this->wirelessProperties,
|
||||
&DBusPropertyGroup::getAllFinished,
|
||||
this,
|
||||
&NMWirelessDevice::initWireless,
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
|
||||
QObject::connect(&this->mScanTimer, &QTimer::timeout, this, &NMWirelessDevice::onScanTimeout);
|
||||
this->mScanTimer.setSingleShot(true);
|
||||
|
||||
this->wirelessProperties.setInterface(this->wirelessProxy);
|
||||
this->wirelessProperties.updateAllViaGetAll();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::initWireless() {
|
||||
// clang-format off
|
||||
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessDevice::onAccessPointAdded);
|
||||
QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessDevice::onAccessPointRemoved);
|
||||
QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::connectionLoaded, this, &NMWirelessDevice::onConnectionLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
|
||||
QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged);
|
||||
// clang-format on
|
||||
this->registerAccessPoints();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) {
|
||||
this->registerAccessPoint(path.path());
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointRemoved(const QDBusObjectPath& path) {
|
||||
auto* ap = this->mAccessPoints.take(path.path());
|
||||
if (!ap) {
|
||||
qCDebug(logNetworkManager) << "Sent removal signal for" << path.path()
|
||||
<< "which is not registered.";
|
||||
return;
|
||||
}
|
||||
delete ap;
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onAccessPointLoaded(NMAccessPoint* ap) {
|
||||
const QString ssid = ap->ssid();
|
||||
if (!ssid.isEmpty()) {
|
||||
auto mode = ap->mode();
|
||||
if (mode == NM80211Mode::Infra) {
|
||||
auto* net = this->mNetworks.value(ssid);
|
||||
if (!net) net = this->registerNetwork(ssid);
|
||||
net->addAccessPoint(ap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) {
|
||||
const ConnectionSettingsMap& settings = conn->settings();
|
||||
// Filter connections that aren't wireless or have missing settings
|
||||
if (settings["connection"]["id"].toString().isEmpty()
|
||||
|| settings["connection"]["uuid"].toString().isEmpty()
|
||||
|| !settings.contains("802-11-wireless")
|
||||
|| settings["802-11-wireless"]["ssid"].toString().isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ssid = settings["802-11-wireless"]["ssid"].toString();
|
||||
const auto mode = settings["802-11-wireless"]["mode"].toString();
|
||||
|
||||
if (mode == "infrastructure") {
|
||||
auto* net = this->mNetworks.value(ssid);
|
||||
if (!net) net = this->registerNetwork(ssid);
|
||||
net->addConnection(conn);
|
||||
|
||||
// Check for active connections that loaded before their respective connection settings
|
||||
auto* active = this->activeConnection();
|
||||
if (active && conn->path() == active->connection().path()) {
|
||||
net->addActiveConnection(active);
|
||||
}
|
||||
}
|
||||
// TODO: Create hotspots when mode == "ap"
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
|
||||
// Find an exisiting network with connection settings that matches the active
|
||||
const QString activeConnPath = active->connection().path();
|
||||
for (const auto& net: this->mNetworks.values()) {
|
||||
for (auto* conn: net->connections()) {
|
||||
if (activeConnPath == conn->path()) {
|
||||
net->addActiveConnection(active);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onScanTimeout() {
|
||||
const QDateTime now = QDateTime::currentDateTime();
|
||||
const QDateTime lastScan = this->bLastScan;
|
||||
const QDateTime lastScanRequest = this->mLastScanRequest;
|
||||
|
||||
if (lastScan.isValid() && lastScan.msecsTo(now) < this->mScanIntervalMs) {
|
||||
// Rate limit if backend last scan property updated within the interval
|
||||
auto diff = static_cast<int>(this->mScanIntervalMs - lastScan.msecsTo(now));
|
||||
this->mScanTimer.start(diff);
|
||||
} else if (lastScanRequest.isValid() && lastScanRequest.msecsTo(now) < this->mScanIntervalMs) {
|
||||
// Rate limit if frontend changes scanner state within the interval
|
||||
auto diff = static_cast<int>(this->mScanIntervalMs - lastScanRequest.msecsTo(now));
|
||||
this->mScanTimer.start(diff);
|
||||
} else {
|
||||
this->wirelessProxy->RequestScan({});
|
||||
this->mLastScanRequest = now;
|
||||
this->mScanTimer.start(this->mScanIntervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
void NMWirelessDevice::onScanningChanged(bool scanning) {
|
||||
scanning ? this->onScanTimeout() : this->mScanTimer.stop();
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerAccessPoints() {
|
||||
auto pending = this->wirelessProxy->GetAllAccessPoints();
|
||||
auto* call = new QDBusPendingCallWatcher(pending, this);
|
||||
|
||||
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
|
||||
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
|
||||
|
||||
if (reply.isError()) {
|
||||
qCWarning(logNetworkManager)
|
||||
<< "Failed to get all access points: " << reply.error().message();
|
||||
} else {
|
||||
for (const QDBusObjectPath& devicePath: reply.value()) {
|
||||
this->registerAccessPoint(devicePath.path());
|
||||
}
|
||||
}
|
||||
|
||||
delete call;
|
||||
};
|
||||
|
||||
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerAccessPoint(const QString& path) {
|
||||
if (this->mAccessPoints.contains(path)) {
|
||||
qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
auto* ap = new NMAccessPoint(path, this);
|
||||
|
||||
if (!ap->isValid()) {
|
||||
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
|
||||
delete ap;
|
||||
return;
|
||||
}
|
||||
|
||||
this->mAccessPoints.insert(path, ap);
|
||||
QObject::connect(
|
||||
ap,
|
||||
&NMAccessPoint::loaded,
|
||||
this,
|
||||
[this, ap]() { emit this->accessPointLoaded(ap); },
|
||||
Qt::SingleShotConnection
|
||||
);
|
||||
ap->bindableSecurity().setBinding([this, ap]() {
|
||||
return findBestWirelessSecurity(
|
||||
this->bCapabilities,
|
||||
ap->mode() == NM80211Mode::Adhoc,
|
||||
ap->flags(),
|
||||
ap->wpaFlags(),
|
||||
ap->rsnFlags()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
|
||||
auto* net = new NMWirelessNetwork(ssid, this);
|
||||
|
||||
// To avoid exposing outdated state to the frontend, filter the backend networks to only show
|
||||
// the known or currently connected networks when the scanner is off.
|
||||
auto visible = [this, net]() {
|
||||
return this->bScanning || net->state() == NMConnectionState::Activated || net->known();
|
||||
};
|
||||
auto onVisibilityChanged = [this, net](bool visible) {
|
||||
visible ? this->registerFrontendNetwork(net) : this->removeFrontendNetwork(net);
|
||||
};
|
||||
|
||||
net->bindableVisible().setBinding(visible);
|
||||
net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
|
||||
QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork);
|
||||
QObject::connect(net, &NMWirelessNetwork::visibilityChanged, this, onVisibilityChanged);
|
||||
|
||||
this->mNetworks.insert(ssid, net);
|
||||
if (net->visible()) this->registerFrontendNetwork(net);
|
||||
return net;
|
||||
}
|
||||
|
||||
void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) {
|
||||
auto ssid = net->ssid();
|
||||
auto* frontendNet = new WifiNetwork(ssid, net);
|
||||
|
||||
// Bind WifiNetwork to NMWirelessNetwork
|
||||
auto translateSignal = [net]() { return net->signalStrength() / 100.0; };
|
||||
auto translateState = [net]() { return net->state() == NMConnectionState::Activated; };
|
||||
frontendNet->bindableSignalStrength().setBinding(translateSignal);
|
||||
frontendNet->bindableConnected().setBinding(translateState);
|
||||
frontendNet->bindableKnown().setBinding([net]() { return net->known(); });
|
||||
frontendNet->bindableNmReason().setBinding([net]() { return net->reason(); });
|
||||
frontendNet->bindableSecurity().setBinding([net]() { return net->security(); });
|
||||
frontendNet->bindableState().setBinding([net]() {
|
||||
return static_cast<NetworkState::Enum>(net->state());
|
||||
});
|
||||
|
||||
QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() {
|
||||
if (net->referenceConnection()) {
|
||||
emit this->activateConnection(
|
||||
QDBusObjectPath(net->referenceConnection()->path()),
|
||||
QDBusObjectPath(this->path())
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (net->referenceAp()) {
|
||||
emit this->addAndActivateConnection(
|
||||
ConnectionSettingsMap(),
|
||||
QDBusObjectPath(this->path()),
|
||||
QDBusObjectPath(net->referenceAp()->path())
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
frontendNet,
|
||||
&WifiNetwork::requestDisconnect,
|
||||
this,
|
||||
&NMWirelessDevice::disconnect
|
||||
);
|
||||
|
||||
QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget);
|
||||
|
||||
this->mFrontendNetworks.insert(ssid, frontendNet);
|
||||
emit this->networkAdded(frontendNet);
|
||||
}
|
||||
|
||||
void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) {
|
||||
auto* frontendNet = this->mFrontendNetworks.take(net->ssid());
|
||||
if (frontendNet) {
|
||||
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);
|
||||
delete net;
|
||||
};
|
||||
}
|
||||
|
||||
bool NMWirelessDevice::isValid() const {
|
||||
return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid());
|
||||
}
|
||||
|
||||
} // namespace qs::network
|
||||
|
||||
namespace qs::dbus {
|
||||
|
||||
DBusResult<qs::network::NMWirelessCapabilities::Enum>
|
||||
DBusDataTransform<qs::network::NMWirelessCapabilities::Enum>::fromWire(quint32 wire) {
|
||||
return DBusResult(static_cast<qs::network::NMWirelessCapabilities::Enum>(wire));
|
||||
}
|
||||
|
||||
DBusResult<QDateTime> DBusDataTransform<QDateTime>::fromWire(qint64 wire) {
|
||||
return DBusResult(qs::network::clockBootTimeToDateTime(wire));
|
||||
}
|
||||
|
||||
} // namespace qs::dbus
|
||||
166
src/network/nm/wireless.hpp
Normal file
166
src/network/nm/wireless.hpp
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
#pragma once
|
||||
|
||||
#include <qdbusextratypes.h>
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../wifi.hpp"
|
||||
#include "accesspoint.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "dbus_nm_wireless.h"
|
||||
#include "device.hpp"
|
||||
#include "enums.hpp"
|
||||
|
||||
namespace qs::dbus {
|
||||
template <>
|
||||
struct DBusDataTransform<qs::network::NMWirelessCapabilities::Enum> {
|
||||
using Wire = quint32;
|
||||
using Data = qs::network::NMWirelessCapabilities::Enum;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DBusDataTransform<QDateTime> {
|
||||
using Wire = qint64;
|
||||
using Data = QDateTime;
|
||||
static DBusResult<Data> fromWire(Wire wire);
|
||||
};
|
||||
|
||||
} // namespace qs::dbus
|
||||
namespace qs::network {
|
||||
|
||||
// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMConnectionSetting objects.
|
||||
class NMWirelessNetwork: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr);
|
||||
|
||||
void addAccessPoint(NMAccessPoint* ap);
|
||||
void addConnection(NMConnectionSettings* conn);
|
||||
void addActiveConnection(NMActiveConnection* active);
|
||||
void forget();
|
||||
|
||||
[[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; };
|
||||
[[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; };
|
||||
[[nodiscard]] NMConnectionSettings* referenceConnection() const { return this->mReferenceConn; };
|
||||
[[nodiscard]] QList<NMAccessPoint*> accessPoints() const { return this->mAccessPoints.values(); };
|
||||
[[nodiscard]] QList<NMConnectionSettings*> connections() const {
|
||||
return this->mConnections.values();
|
||||
}
|
||||
[[nodiscard]] QBindable<QString> bindableActiveApPath() { return &this->bActiveApPath; };
|
||||
[[nodiscard]] QBindable<bool> bindableVisible() { return &this->bVisible; };
|
||||
[[nodiscard]] bool visible() const { return this->bVisible; };
|
||||
|
||||
signals:
|
||||
void disappeared();
|
||||
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 capabilitiesChanged(NMWirelessCapabilities::Enum caps);
|
||||
void activeApPathChanged(QString path);
|
||||
|
||||
private:
|
||||
void updateReferenceAp();
|
||||
void updateReferenceConnection();
|
||||
|
||||
QString mSsid;
|
||||
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||
QHash<QString, NMConnectionSettings*> mConnections;
|
||||
NMAccessPoint* mReferenceAp = nullptr;
|
||||
NMConnectionSettings* mReferenceConn = 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, 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).
|
||||
class NMWirelessDevice: public NMDevice {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool isValid() const override;
|
||||
[[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; };
|
||||
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; };
|
||||
[[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; };
|
||||
[[nodiscard]] QBindable<bool> bindableScanning() { return &this->bScanning; };
|
||||
|
||||
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);
|
||||
void activeAccessPointChanged(const QDBusObjectPath& path);
|
||||
void modeChanged(NM80211Mode::Enum mode);
|
||||
|
||||
private slots:
|
||||
void onAccessPointAdded(const QDBusObjectPath& path);
|
||||
void onAccessPointRemoved(const QDBusObjectPath& path);
|
||||
void onAccessPointLoaded(NMAccessPoint* ap);
|
||||
void onConnectionLoaded(NMConnectionSettings* conn);
|
||||
void onActiveConnectionLoaded(NMActiveConnection* active);
|
||||
void onScanTimeout();
|
||||
void onScanningChanged(bool scanning);
|
||||
|
||||
private:
|
||||
void registerAccessPoint(const QString& path);
|
||||
void registerFrontendNetwork(NMWirelessNetwork* net);
|
||||
void removeFrontendNetwork(NMWirelessNetwork* net);
|
||||
void removeNetwork();
|
||||
bool checkVisibility(WifiNetwork* net);
|
||||
void registerAccessPoints();
|
||||
void initWireless();
|
||||
NMWirelessNetwork* registerNetwork(const QString& ssid);
|
||||
|
||||
QHash<QString, NMAccessPoint*> mAccessPoints;
|
||||
QHash<QString, NMWirelessNetwork*> mNetworks;
|
||||
QHash<QString, WifiNetwork*> mFrontendNetworks;
|
||||
|
||||
QDateTime mLastScanRequest;
|
||||
QTimer mScanTimer;
|
||||
qint32 mScanIntervalMs = 10001;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, bool, bScanning, &NMWirelessDevice::scanningChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDateTime, bLastScan, &NMWirelessDevice::lastScanChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessDevice::capabilitiesChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDBusObjectPath, bActiveAccessPoint, &NMWirelessDevice::activeAccessPointChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NM80211Mode::Enum, bMode, &NMWirelessDevice::modeChanged);
|
||||
|
||||
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wirelessProperties);
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pLastScan, bLastScan, wirelessProperties, "LastScan");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pCapabilities, bCapabilities, wirelessProperties, "WirelessCapabilities");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint");
|
||||
QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pMode, bMode, wirelessProperties, "Mode");
|
||||
// clang-format on
|
||||
|
||||
DBusNMWirelessProxy* wirelessProxy = nullptr;
|
||||
};
|
||||
|
||||
} // namespace qs::network
|
||||
155
src/network/test/manual/network.qml
Normal file
155
src/network/test/manual/network.qml
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Networking
|
||||
|
||||
FloatingWindow {
|
||||
color: contentItem.palette.window
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 5
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Label {
|
||||
text: "WiFi"
|
||||
font.bold: true
|
||||
font.pointSize: 12
|
||||
}
|
||||
CheckBox {
|
||||
text: "Software"
|
||||
checked: Networking.wifiEnabled
|
||||
onClicked: Networking.wifiEnabled = !Networking.wifiEnabled
|
||||
}
|
||||
CheckBox {
|
||||
enabled: false
|
||||
text: "Hardware"
|
||||
checked: Networking.wifiHardwareEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
clip: true
|
||||
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)})` }
|
||||
}
|
||||
RowLayout {
|
||||
Label {
|
||||
text: DeviceConnectionState.toString(modelData.state)
|
||||
color: modelData.connected ? palette.link : palette.placeholderText
|
||||
}
|
||||
Label {
|
||||
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting)
|
||||
text: `(${NMDeviceState.toString(modelData.nmState)})`
|
||||
}
|
||||
Button {
|
||||
visible: modelData.state == DeviceConnectionState.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: {
|
||||
if (modelData.type !== DeviceType.Wifi) return []
|
||||
return [...modelData.networks.values].sort((a, b) => {
|
||||
if (a.connected !== b.connected) {
|
||||
return b.connected - a.connected
|
||||
}
|
||||
return b.signalStrength - a.signalStrength
|
||||
})
|
||||
}
|
||||
|
||||
WrapperRectangle {
|
||||
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
|
||||
}
|
||||
}
|
||||
Label {
|
||||
visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None)
|
||||
text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}`
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Button {
|
||||
text: "Connect"
|
||||
onClicked: modelData.connect()
|
||||
visible: !modelData.connected
|
||||
}
|
||||
Button {
|
||||
text: "Disconnect"
|
||||
onClicked: modelData.disconnect()
|
||||
visible: modelData.connected
|
||||
}
|
||||
Button {
|
||||
text: "Forget"
|
||||
onClicked: modelData.forget()
|
||||
visible: modelData.known
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
src/network/wifi.cpp
Normal file
139
src/network/wifi.cpp
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#include "wifi.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qstring.h>
|
||||
#include <qstringliteral.h>
|
||||
|
||||
#include "../core/logcat.hpp"
|
||||
#include "device.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
namespace {
|
||||
QS_LOGGING_CATEGORY(logWifi, "quickshell.network.wifi", QtWarningMsg);
|
||||
} // namespace
|
||||
|
||||
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
|
||||
switch (type) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
|
||||
case Sae: return QStringLiteral("WPA3");
|
||||
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
|
||||
case Wpa2Psk: return QStringLiteral("WPA2");
|
||||
case WpaEap: return QStringLiteral("WPA Enterprise");
|
||||
case WpaPsk: return QStringLiteral("WPA");
|
||||
case StaticWep: return QStringLiteral("WEP");
|
||||
case DynamicWep: return QStringLiteral("Dynamic WEP");
|
||||
case Leap: return QStringLiteral("LEAP");
|
||||
case Owe: return QStringLiteral("OWE");
|
||||
case Open: return QStringLiteral("Open");
|
||||
default: return QStringLiteral("Unknown");
|
||||
}
|
||||
}
|
||||
|
||||
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
|
||||
switch (mode) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case AdHoc: return QStringLiteral("Ad-Hoc");
|
||||
case Station: return QStringLiteral("Station");
|
||||
case AccessPoint: return QStringLiteral("Access Point");
|
||||
case Mesh: return QStringLiteral("Mesh");
|
||||
default: return QStringLiteral("Unknown");
|
||||
};
|
||||
}
|
||||
|
||||
QString NMConnectionStateReason::toString(NMConnectionStateReason::Enum reason) {
|
||||
switch (reason) {
|
||||
case Unknown: return QStringLiteral("Unknown");
|
||||
case None: return QStringLiteral("No reason");
|
||||
case UserDisconnected: return QStringLiteral("User disconnection");
|
||||
case DeviceDisconnected:
|
||||
return QStringLiteral("The device the connection was using was disconnected.");
|
||||
case ServiceStopped:
|
||||
return QStringLiteral("The service providing the VPN connection was stopped.");
|
||||
case IpConfigInvalid:
|
||||
return QStringLiteral("The IP config of the active connection was invalid.");
|
||||
case ConnectTimeout:
|
||||
return QStringLiteral("The connection attempt to the VPN service timed out.");
|
||||
case ServiceStartTimeout:
|
||||
return QStringLiteral(
|
||||
"A timeout occurred while starting the service providing the VPN connection."
|
||||
);
|
||||
case ServiceStartFailed:
|
||||
return QStringLiteral("Starting the service providing the VPN connection failed.");
|
||||
case NoSecrets: return QStringLiteral("Necessary secrets for the connection were not provided.");
|
||||
case LoginFailed: return QStringLiteral("Authentication to the server failed.");
|
||||
case ConnectionRemoved:
|
||||
return QStringLiteral("Necessary secrets for the connection were not provided.");
|
||||
case DependencyFailed:
|
||||
return QStringLiteral("Master connection of this connection failed to activate.");
|
||||
case DeviceRealizeFailed: return QStringLiteral("Could not create the software device link.");
|
||||
case DeviceRemoved: return QStringLiteral("The device this connection depended on disappeared.");
|
||||
default: return QStringLiteral("Unknown");
|
||||
};
|
||||
};
|
||||
|
||||
WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {};
|
||||
|
||||
void WifiNetwork::connect() {
|
||||
if (this->bConnected) {
|
||||
qCCritical(logWifi) << this << "is already connected.";
|
||||
return;
|
||||
}
|
||||
|
||||
this->requestConnect();
|
||||
}
|
||||
|
||||
void WifiNetwork::disconnect() {
|
||||
if (!this->bConnected) {
|
||||
qCCritical(logWifi) << this << "is not currently connected";
|
||||
return;
|
||||
}
|
||||
|
||||
this->requestDisconnect();
|
||||
}
|
||||
|
||||
void WifiNetwork::forget() { this->requestForget(); }
|
||||
|
||||
WifiDevice::WifiDevice(QObject* parent): NetworkDevice(DeviceType::Wifi, parent) {};
|
||||
|
||||
void WifiDevice::setScannerEnabled(bool enabled) {
|
||||
if (this->bScannerEnabled == enabled) return;
|
||||
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) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
|
||||
if (network) {
|
||||
debug.nospace() << "WifiNetwork(" << static_cast<const void*>(network)
|
||||
<< ", name=" << network->name() << ")";
|
||||
} else {
|
||||
debug << "WifiNetwork(nullptr)";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device) {
|
||||
auto saver = QDebugStateSaver(debug);
|
||||
|
||||
if (device) {
|
||||
debug.nospace() << "WifiDevice(" << static_cast<const void*>(device)
|
||||
<< ", name=" << device->name() << ")";
|
||||
} else {
|
||||
debug << "WifiDevice(nullptr)";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
186
src/network/wifi.hpp
Normal file
186
src/network/wifi.hpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/model.hpp"
|
||||
#include "device.hpp"
|
||||
#include "network.hpp"
|
||||
|
||||
namespace qs::network {
|
||||
|
||||
///! The security type of a wifi network.
|
||||
class WifiSecurityType: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Wpa3SuiteB192 = 0,
|
||||
Sae = 1,
|
||||
Wpa2Eap = 2,
|
||||
Wpa2Psk = 3,
|
||||
WpaEap = 4,
|
||||
WpaPsk = 5,
|
||||
StaticWep = 6,
|
||||
DynamicWep = 7,
|
||||
Leap = 8,
|
||||
Owe = 9,
|
||||
Open = 10,
|
||||
Unknown = 11,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(WifiSecurityType::Enum type);
|
||||
};
|
||||
|
||||
///! The 802.11 mode of a wifi device.
|
||||
class WifiDeviceMode: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
/// The device is part of an Ad-Hoc network without a central access point.
|
||||
AdHoc = 0,
|
||||
/// The device is a station that can connect to networks.
|
||||
Station = 1,
|
||||
/// The device is a local hotspot/access point.
|
||||
AccessPoint = 2,
|
||||
/// The device is an 802.11s mesh point.
|
||||
Mesh = 3,
|
||||
/// The device mode is unknown.
|
||||
Unknown = 4,
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode);
|
||||
};
|
||||
|
||||
///! NetworkManager-specific reason for a WifiNetworks connection state.
|
||||
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason.
|
||||
class NMConnectionStateReason: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_SINGLETON;
|
||||
|
||||
public:
|
||||
enum Enum : quint8 {
|
||||
Unknown = 0,
|
||||
None = 1,
|
||||
UserDisconnected = 2,
|
||||
DeviceDisconnected = 3,
|
||||
ServiceStopped = 4,
|
||||
IpConfigInvalid = 5,
|
||||
ConnectTimeout = 6,
|
||||
ServiceStartTimeout = 7,
|
||||
ServiceStartFailed = 8,
|
||||
NoSecrets = 9,
|
||||
LoginFailed = 10,
|
||||
ConnectionRemoved = 11,
|
||||
DependencyFailed = 12,
|
||||
DeviceRealizeFailed = 13,
|
||||
DeviceRemoved = 14
|
||||
};
|
||||
Q_ENUM(Enum);
|
||||
Q_INVOKABLE static QString toString(NMConnectionStateReason::Enum reason);
|
||||
};
|
||||
|
||||
///! An available wifi network.
|
||||
class WifiNetwork: public Network {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("WifiNetwork can only be acquired through WifiDevice");
|
||||
// clang-format off
|
||||
/// The current signal strength of the network, from 0.0 to 1.0.
|
||||
Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength);
|
||||
/// True if the wifi network has known connection settings saved.
|
||||
Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown);
|
||||
/// The security type of the wifi network.
|
||||
Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity);
|
||||
/// A specific reason for the connection state when the backend is NetworkManager.
|
||||
Q_PROPERTY(NMConnectionStateReason::Enum nmReason READ default NOTIFY nmReasonChanged BINDABLE bindableNmReason);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit WifiNetwork(QString ssid, QObject* parent = nullptr);
|
||||
|
||||
/// Attempt to connect to the wifi network.
|
||||
///
|
||||
/// > [!WARNING] Quickshell does not yet provide a NetworkManager authentication agent,
|
||||
/// > meaning another agent will need to be active to enter passwords for unsaved networks.
|
||||
Q_INVOKABLE void connect();
|
||||
/// Disconnect from the wifi network.
|
||||
Q_INVOKABLE void disconnect();
|
||||
/// Forget all connection settings for this wifi network.
|
||||
Q_INVOKABLE void forget();
|
||||
|
||||
QBindable<qreal> bindableSignalStrength() { return &this->bSignalStrength; }
|
||||
QBindable<bool> bindableKnown() { return &this->bKnown; }
|
||||
QBindable<NMConnectionStateReason::Enum> bindableNmReason() { return &this->bNmReason; }
|
||||
QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; }
|
||||
|
||||
signals:
|
||||
void requestConnect();
|
||||
void requestDisconnect();
|
||||
void requestForget();
|
||||
void signalStrengthChanged();
|
||||
void knownChanged();
|
||||
void securityChanged();
|
||||
void nmReasonChanged();
|
||||
|
||||
private:
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, qreal, bSignalStrength, &WifiNetwork::signalStrengthChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bKnown, &WifiNetwork::knownChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, NMConnectionStateReason::Enum, bNmReason, &WifiNetwork::nmReasonChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, WifiSecurityType::Enum, bSecurity, &WifiNetwork::securityChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
///! Wireless variant of a NetworkDevice.
|
||||
class WifiDevice: public NetworkDevice {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
|
||||
// clang-format off
|
||||
/// A list of this available and 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);
|
||||
/// The 802.11 mode the device is in.
|
||||
Q_PROPERTY(WifiDeviceMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode);
|
||||
// clang-format on
|
||||
|
||||
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);
|
||||
QBindable<WifiDeviceMode::Enum> bindableMode() { return &this->bMode; }
|
||||
|
||||
signals:
|
||||
void modeChanged();
|
||||
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
|
||||
|
||||
QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network);
|
||||
QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device);
|
||||
Loading…
Add table
Add a link
Reference in a new issue