Skip to content

Commit 9c70218

Browse files
committed
network: add WifiScanner
- Add WifiScanner to replace requestScan in API - Implement NM scanner that avoids rate limit - Add conditional visibility to networks based on scanner state - Add gates to backend-specific props in the manual test
1 parent 6e227a7 commit 9c70218

File tree

8 files changed

+192
-68
lines changed

8 files changed

+192
-68
lines changed

src/network/nm/backend.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ void NetworkManager::registerWifiDevice(const QString& path) {
139139
}
140140

141141
auto* device = new WifiDevice(this);
142+
auto* scanner = device->wifiScanner();
142143
wireless->setParent(device);
143144
this->mDeviceHash.insert(path, device);
144145

@@ -154,13 +155,12 @@ void NetworkManager::registerWifiDevice(const QString& path) {
154155
case 110 ... 120: return DeviceConnectionState::Disconnecting;
155156
}
156157
});
157-
device->bindableScanning().setBinding([wireless]() { return wireless->scanning(); });
158158
// clang-format off
159159
QObject::connect(wireless, &NMWirelessDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
160160
QObject::connect(wireless, &NMWirelessDevice::activateConnection, this, &NetworkManager::activateConnection);
161-
QObject::connect(wireless, &NMWirelessDevice::wifiNetworkAdded, device, &WifiDevice::networkAdded);
162-
QObject::connect(wireless, &NMWirelessDevice::wifiNetworkRemoved, device, &WifiDevice::networkRemoved);
163-
QObject::connect(device, &WifiDevice::requestScan, wireless, &NMWirelessDevice::scan);
161+
QObject::connect(wireless, &NMWirelessDevice::networkAdded, device, &WifiDevice::networkAdded);
162+
QObject::connect(wireless, &NMWirelessDevice::networkRemoved, device, &WifiDevice::networkRemoved);
163+
QObject::connect(scanner, &WifiScanner::requestEnabled, wireless, &NMWirelessDevice::handleScanner);
164164
QObject::connect(device, &WifiDevice::requestDisconnect, wireless, &NMWirelessDevice::disconnect);
165165
// clang-format on
166166

src/network/nm/utils.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
#include "utils.hpp"
22

3+
// We depend on non-std Linux extensions that ctime doesn't put in the global namespace
4+
// NOLINTNEXTLINE(modernize-deprecated-headers)
5+
#include <time.h>
6+
37
#include <qcontainerfwd.h>
8+
#include <qdatetime.h>
49
#include <qdbusservicewatcher.h>
510
#include <qobject.h>
611
#include <qqmlintegration.h>
12+
#include <qtypes.h>
713

814
#include "../wifi.hpp"
915
#include "dbus_types.hpp"
@@ -218,4 +224,25 @@ WifiSecurityType::Enum findBestWirelessSecurity(
218224
return WifiSecurityType::Unknown;
219225
}
220226

227+
// NOLINTBEGIN
228+
QDateTime clockBootTimeToDateTime(qint64 clockBootTime) {
229+
clockid_t clkId = CLOCK_BOOTTIME;
230+
struct timespec tp {};
231+
232+
const QDateTime now = QDateTime::currentDateTime();
233+
int r = clock_gettime(clkId, &tp);
234+
if (r == -1 && errno == EINVAL) {
235+
clkId = CLOCK_MONOTONIC;
236+
r = clock_gettime(clkId, &tp);
237+
}
238+
239+
// Convert to milliseconds
240+
const qint64 nowInMs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
241+
242+
// Return a QDateTime of the millisecond diff
243+
const qint64 offset = clockBootTime - nowInMs;
244+
return QDateTime::fromMSecsSinceEpoch(now.toMSecsSinceEpoch() + offset);
245+
}
246+
// NOLINTEND
247+
221248
} // namespace qs::network

src/network/nm/utils.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#pragma once
32

43
#include <qcontainerfwd.h>
@@ -41,4 +40,6 @@ WifiSecurityType::Enum findBestWirelessSecurity(
4140
NM80211ApSecurityFlags::Enum apRsn
4241
);
4342

43+
QDateTime clockBootTimeToDateTime(qint64 clockBootTime);
44+
4445
} // namespace qs::network

src/network/nm/wireless.cpp

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "wireless.hpp"
22
#include <utility>
33

4+
#include <qdatetime.h>
45
#include <qdbusconnection.h>
56
#include <qdbusextratypes.h>
67
#include <qdbuspendingcall.h>
@@ -181,7 +182,9 @@ void NMWirelessNetwork::removeActiveConnection() {
181182
}
182183
};
183184

184-
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent): NMDevice(path, parent) {
185+
NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent)
186+
: NMDevice(path, parent)
187+
, mScanTimer(this) {
185188
this->wirelessProxy = new DBusNMWirelessProxy(
186189
"org.freedesktop.NetworkManager",
187190
path,
@@ -202,6 +205,9 @@ NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent): NMDevi
202205
Qt::SingleShotConnection
203206
);
204207

208+
QObject::connect(&this->mScanTimer, &QTimer::timeout, this, &NMWirelessDevice::onScanTimeout);
209+
this->mScanTimer.setSingleShot(true);
210+
205211
this->wirelessProperties.setInterface(this->wirelessProxy);
206212
this->wirelessProperties.updateAllViaGetAll();
207213
}
@@ -213,7 +219,6 @@ void NMWirelessDevice::initWireless() {
213219
QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded);
214220
QObject::connect(this, &NMWirelessDevice::connectionLoaded, this, &NMWirelessDevice::onConnectionLoaded);
215221
QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded);
216-
QObject::connect(this, &NMWirelessDevice::lastScanChanged, this, [this]() { this->bScanning = false; });
217222
// clang-format on
218223
this->registerAccessPoints();
219224
}
@@ -279,25 +284,47 @@ void NMWirelessDevice::registerAccessPoint(const QString& path) {
279284
);
280285
}
281286

287+
// Only make WifiNetworks visible to the frontend WifiDevice when
288+
// scanning is enabled or the network is connected or the network has known settings.
289+
void NMWirelessDevice::updateNetworkVisibility(WifiNetwork* net) {
290+
const bool show = this->mScanning || net->connected() || net->known();
291+
const bool visible = this->mVisibleNetworks.contains(net);
292+
293+
if (show && !visible) {
294+
this->mVisibleNetworks.insert(net);
295+
emit this->networkAdded(net);
296+
} else if (!show && visible) {
297+
this->mVisibleNetworks.remove(net);
298+
emit this->networkRemoved(net);
299+
}
300+
}
301+
282302
NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
283303
auto* backend = new NMWirelessNetwork(ssid, this);
284304
backend->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); });
285305
backend->bindableCapabilities().setBinding([this]() { return this->capabilities(); });
286306

287307
auto* frontend = new WifiNetwork(ssid, this);
288-
frontend->bindableSignalStrength().setBinding([backend]() { return backend->signalStrength()/100.0; });
308+
frontend->bindableSignalStrength().setBinding([backend]() {
309+
return backend->signalStrength() / 100.0;
310+
});
289311
frontend->bindableConnected().setBinding([backend]() {
290-
return backend->state() != NMConnectionState::Deactivated;
312+
return backend->state() == NMConnectionState::Activated;
291313
});
292314
frontend->bindableKnown().setBinding([backend]() { return backend->known(); });
293315
frontend->bindableNmReason().setBinding([backend]() { return backend->reason(); });
294316
frontend->bindableSecurity().setBinding([backend]() { return backend->security(); });
295317
QObject::connect(backend, &NMWirelessNetwork::disappeared, this, [this, frontend, backend]() {
296318
QObject::disconnect(backend, nullptr, nullptr, nullptr);
297-
emit this->wifiNetworkRemoved(frontend);
319+
QObject::disconnect(frontend, nullptr, nullptr, nullptr);
298320
this->mBackendNetworks.remove(backend->ssid());
299-
delete backend;
321+
this->mNetworks.remove(frontend->name());
322+
if (this->mVisibleNetworks.contains(frontend)) {
323+
this->mVisibleNetworks.remove(frontend);
324+
emit this->networkRemoved(frontend);
325+
}
300326
delete frontend;
327+
delete backend;
301328
});
302329
QObject::connect(frontend, &WifiNetwork::requestConnect, this, [this, backend]() {
303330
if (backend->referenceConnection()) {
@@ -315,9 +342,16 @@ NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) {
315342
);
316343
}
317344
});
345+
QObject::connect(frontend, &WifiNetwork::connectedChanged, this, [this, frontend]() {
346+
updateNetworkVisibility(frontend);
347+
});
348+
QObject::connect(frontend, &WifiNetwork::knownChanged, this, [this, frontend]() {
349+
updateNetworkVisibility(frontend);
350+
});
318351

319352
this->mBackendNetworks.insert(ssid, backend);
320-
emit this->wifiNetworkAdded(frontend);
353+
this->mNetworks.insert(ssid, frontend);
354+
this->updateNetworkVisibility(frontend);
321355
return backend;
322356
}
323357

@@ -343,7 +377,7 @@ void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) {
343377
return;
344378
}
345379

346-
const QString ssid = settings["802-11-wireless"]["ssid"].toString();
380+
const auto ssid = settings["802-11-wireless"]["ssid"].toString();
347381
auto* net = this->mBackendNetworks.value(ssid);
348382
if (!net) net = this->registerNetwork(ssid);
349383

@@ -366,9 +400,33 @@ void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) {
366400
}
367401
}
368402

369-
void NMWirelessDevice::scan() {
370-
this->wirelessProxy->RequestScan({});
371-
this->bScanning = true;
403+
void NMWirelessDevice::onScanTimeout() {
404+
const QDateTime now = QDateTime::currentDateTime();
405+
const QDateTime lastScan = this->bLastScan;
406+
const QDateTime lastScanRequest = this->mLastScanRequest;
407+
408+
if (lastScan.isValid() && lastScan.msecsTo(now) < this->mScanIntervalMs) {
409+
// Rate limit if backend scan property updated within the interval
410+
auto diff = static_cast<int>(this->mScanIntervalMs - lastScan.msecsTo(now));
411+
this->mScanTimer.start(diff);
412+
} else if (lastScanRequest.isValid() && lastScanRequest.msecsTo(now) < this->mScanIntervalMs) {
413+
// Rate limit if frontend changes scanner state within the interval
414+
auto diff = static_cast<int>(this->mScanIntervalMs - lastScanRequest.msecsTo(now));
415+
this->mScanTimer.start(diff);
416+
} else {
417+
this->wirelessProxy->RequestScan({});
418+
this->mLastScanRequest = now;
419+
this->mScanTimer.start(this->mScanIntervalMs);
420+
}
421+
}
422+
423+
void NMWirelessDevice::handleScanner(bool enabled) {
424+
if (this->mScanning == enabled) return;
425+
this->mScanning = enabled;
426+
for (WifiNetwork* net: this->mNetworks) {
427+
updateNetworkVisibility(net);
428+
}
429+
enabled ? this->onScanTimeout() : this->mScanTimer.stop();
372430
}
373431

374432
bool NMWirelessDevice::isWirelessValid() const {
@@ -384,4 +442,8 @@ DBusDataTransform<qs::network::NMWirelessCapabilities::Enum>::fromWire(quint32 w
384442
return DBusResult(static_cast<qs::network::NMWirelessCapabilities::Enum>(wire));
385443
}
386444

445+
DBusResult<QDateTime> DBusDataTransform<QDateTime>::fromWire(qint64 wire) {
446+
return DBusResult(network::clockBootTimeToDateTime(wire));
447+
}
448+
387449
} // namespace qs::dbus

src/network/nm/wireless.hpp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ struct DBusDataTransform<qs::network::NMWirelessCapabilities::Enum> {
2222
static DBusResult<Data> fromWire(Wire wire);
2323
};
2424

25+
template <>
26+
struct DBusDataTransform<QDateTime> {
27+
using Wire = qint64;
28+
using Data = QDateTime;
29+
static DBusResult<Data> fromWire(Wire wire);
30+
};
31+
2532
} // namespace qs::dbus
2633
namespace qs::network {
2734

@@ -97,49 +104,55 @@ class NMWirelessDevice: public NMDevice {
97104
explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr);
98105

99106
[[nodiscard]] bool isWirelessValid() const;
100-
[[nodiscard]] qint64 lastScan() { return this->bLastScan; };
101107
[[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; };
102108
[[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; };
103-
[[nodiscard]] bool scanning() { return this->bScanning; };
104109

105110
signals:
106111
void accessPointLoaded(NMAccessPoint* ap);
107112
void accessPointRemoved(NMAccessPoint* ap);
108-
void wifiNetworkAdded(WifiNetwork* net);
109-
void wifiNetworkRemoved(WifiNetwork* net);
113+
void networkAdded(WifiNetwork* net);
114+
void networkRemoved(WifiNetwork* net);
110115
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
111116
void addAndActivateConnection(
112117
const ConnectionSettingsMap& settings,
113118
const QDBusObjectPath& devPath,
114119
const QDBusObjectPath& apPath
115120
);
116-
void lastScanChanged(qint64 lastScan);
121+
void lastScanChanged(QDateTime lastScan);
117122
void capabilitiesChanged(NMWirelessCapabilities::Enum caps);
118123
void activeAccessPointChanged(const QDBusObjectPath& path);
119-
void scanningChanged(bool scanning);
120124

121125
public slots:
122-
void scan();
126+
void handleScanner(bool enabled);
123127

124128
private slots:
125129
void onAccessPointPathAdded(const QDBusObjectPath& path);
126130
void onAccessPointPathRemoved(const QDBusObjectPath& path);
127131
void onAccessPointLoaded(NMAccessPoint* ap);
128132
void onConnectionLoaded(NMConnectionSettings* conn);
129133
void onActiveConnectionLoaded(NMActiveConnection* active);
134+
void onScanTimeout();
130135

131136
private:
132137
void registerAccessPoint(const QString& path);
138+
void updateNetworkVisibility(WifiNetwork* net);
133139
void registerAccessPoints();
134140
void initWireless();
135141
NMWirelessNetwork* registerNetwork(const QString& ssid);
136142

137143
QHash<QString, NMAccessPoint*> mAccessPoints;
138144
QHash<QString, NMWirelessNetwork*> mBackendNetworks;
139145

146+
QHash<QString, WifiNetwork*> mNetworks;
147+
QSet<WifiNetwork*> mVisibleNetworks;
148+
149+
QDateTime mLastScanRequest;
150+
QTimer mScanTimer;
151+
bool mScanning = false;
152+
qint32 mScanIntervalMs = 10001;
153+
140154
// clang-format off
141-
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, qint64, bLastScan, &NMWirelessDevice::lastScanChanged);
142-
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, bool, bScanning, &NMWirelessDevice::scanningChanged);
155+
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDateTime, bLastScan, &NMWirelessDevice::lastScanChanged);
143156
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessDevice::capabilitiesChanged);
144157
Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDBusObjectPath, bActiveAccessPoint, &NMWirelessDevice::activeAccessPointChanged);
145158

src/network/test/manual/network.qml

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ FloatingWindow {
3737
clip: true
3838
Layout.fillWidth: true
3939
Layout.fillHeight: true
40-
model: {
41-
return Network.devices.values.filter(device => device.type === DeviceType.Wifi)
42-
}
40+
model: Network.devices
4341

4442
delegate: WrapperRectangle {
4543
width: parent.width
@@ -48,15 +46,6 @@ FloatingWindow {
4846
border.width: 1
4947
margin: 5
5048

51-
property var sortedNetworks: {
52-
return [...modelData.networks.values].sort((a, b) => {
53-
if (a.connected !== b.connected) {
54-
return b.connected - a.connected
55-
}
56-
return b.signalStrength - a.signalStrength
57-
})
58-
}
59-
6049
ColumnLayout {
6150
Label { text: `Device: ${modelData.name} (Hardware address: ${modelData.address}) (Type: ${DeviceType.toString(modelData.type)})` }
6251
RowLayout {
@@ -65,24 +54,33 @@ FloatingWindow {
6554
color: modelData.state == DeviceConnectionState.Connected ? palette.link : palette.placeholderText
6655
}
6756
Label {
68-
visible: modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting
57+
visible: Network.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting)
6958
text: `(${NMDeviceState.toString(modelData.nmState)})`
7059
}
7160
Button {
7261
visible: modelData.state == DeviceConnectionState.Connected
7362
text: "Disconnect"
7463
onClicked: modelData.disconnect()
7564
}
76-
Button {
77-
text: "Scan"
78-
onClicked: modelData.scan()
79-
visible: modelData.scanning === false
65+
CheckBox {
66+
text: "Scanner"
67+
checked: modelData.wifiScanner.enabled
68+
onClicked: modelData.wifiScanner.enabled = !modelData.wifiScanner.enabled
69+
visible: modelData.type === DeviceType.Wifi
8070
}
8171
}
8272

8373
Repeater {
8474
Layout.fillWidth: true
85-
model: sortedNetworks
75+
model: {
76+
if (modelData.type !== DeviceType.Wifi) return []
77+
return [...modelData.networks.values].sort((a, b) => {
78+
if (a.connected !== b.connected) {
79+
return b.connected - a.connected
80+
}
81+
return b.signalStrength - a.signalStrength
82+
})
83+
}
8684

8785
WrapperRectangle {
8886
Layout.fillWidth: true
@@ -112,7 +110,7 @@ FloatingWindow {
112110
}
113111
}
114112
Label {
115-
visible: modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None
113+
visible: Network.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None)
116114
text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}`
117115
}
118116
}

0 commit comments

Comments
 (0)