Skip to content

Commit 03ef74a

Browse files
committed
fix(#363): fire network monitor on signal strength changes
1 parent f67e19b commit 03ef74a

6 files changed

Lines changed: 101 additions & 19 deletions

File tree

docs/src/advanced/dbus.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ nmrs: nm.list_devices()
5555

5656
```
5757
nmrs: nm.monitor_network_changes(callback)
58-
→ D-Bus: Subscribe to AccessPointAdded/Removed signals
59-
← D-Bus: Signal whenever an AP appears or disappears
58+
→ D-Bus: Subscribe to AccessPointAdded/Removed and AP Strength changes
59+
← D-Bus: Signal whenever an AP appears, disappears, or changes strength
6060
→ nmrs: Invoke callback
6161
```
6262

docs/src/api/network-manager.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ let config = nm.timeout_config();
9191

9292
| Method | Returns | Description |
9393
|--------|---------|-------------|
94-
| `monitor_network_changes(callback)` | `Result<()>` | Watch for AP changes (runs forever) |
94+
| `monitor_network_changes(callback)` | `Result<()>` | Watch for AP and signal strength changes (runs forever) |
9595
| `monitor_device_changes(callback)` | `Result<()>` | Watch for device state changes (runs forever) |
9696

9797
## Thread Safety

docs/src/guide/monitoring.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ nmrs uses D-Bus signals to provide real-time notifications when network state ch
44

55
## Network Change Monitoring
66

7-
Subscribe to network list changes (access points appearing or disappearing):
7+
Subscribe to network changes (access points appearing or disappearing, or signal
8+
strength changing):
89

910
```rust
1011
use nmrs::NetworkManager;
@@ -22,7 +23,7 @@ async fn main() -> nmrs::Result<()> {
2223
}
2324
```
2425

25-
`monitor_network_changes()` subscribes to D-Bus signals for access point additions and removals on all Wi-Fi devices. The callback fires whenever the visible network list changes.
26+
`monitor_network_changes()` subscribes to D-Bus signals for access point additions, removals, and signal strength updates on all Wi-Fi devices. The callback fires whenever the visible network list or signal data changes.
2627

2728
## Device State Monitoring
2829

nmrs/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ All notable changes to the `nmrs` crate will be documented in this file.
1717
- Support for specifying Bluetooth adapter in `BluetoothIdentity` ([#267](https://github.com/cachebag/nmrs/pull/267))
1818

1919
### Fixed
20+
- `monitor_network_changes` now fires for Wi-Fi access point signal strength changes, not only access point additions and removals ([#363](https://github.com/cachebag/nmrs/issues/363))
2021
- Add `Send` bound to monitoring stream trait objects so `monitor_network_changes` and `monitor_device_changes` work with `tokio::spawn` ([#359](https://github.com/cachebag/nmrs/pull/359))
2122
- Line-accurate source locations for `.ovpn` directives and blocks ([#318](https://github.com/cachebag/nmrs/pull/318))
2223
- `key_direction` when nested under `tls_auth` and as a standalone directive ([#320](https://github.com/cachebag/nmrs/pull/320))
@@ -215,4 +216,3 @@ All notable changes to the `nmrs` crate will be documented in this file.
215216
[0.2.0-beta]: https://github.com/cachebag/nmrs/compare/v0.1.1-beta...v0.2.0-beta
216217
[0.1.1-beta]: https://github.com/cachebag/nmrs/compare/v0.1.0-beta...v0.1.1-beta
217218
[0.1.0-beta]: https://github.com/cachebag/nmrs/releases/tag/v0.1.0-beta
218-

nmrs/src/api/network_manager.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -632,9 +632,10 @@ impl NetworkManager {
632632
.await
633633
}
634634
///
635-
/// Subscribes to D-Bus signals for access point additions and removals
636-
/// on all Wi-Fi devices. Invokes the callback whenever the network list
637-
/// changes, enabling live UI updates without polling.
635+
/// Subscribes to D-Bus signals for access point additions, removals, and
636+
/// signal strength changes on all Wi-Fi devices. Invokes the callback
637+
/// whenever the network list or signal data changes, enabling live UI
638+
/// updates without polling.
638639
///
639640
/// This function runs indefinitely until an error occurs. Run it in a
640641
/// background task.

nmrs/src/monitoring/network.rs

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
//! Real-time network monitoring using D-Bus signals.
22
//!
33
//! Provides functionality to monitor access point changes (additions/removals)
4-
//! in real-time without needing to poll. This enables live UI updates.
4+
//! and signal strength changes in real-time without needing to poll. This
5+
//! enables live UI updates.
56
67
use futures::stream::{Stream, StreamExt};
78
use log::{debug, warn};
9+
use std::collections::HashSet;
810
use std::pin::Pin;
911
use tokio::select;
1012
use tokio::sync::watch;
1113
use zbus::Connection;
14+
use zvariant::OwnedObjectPath;
1215

1316
use crate::Result;
1417
use crate::api::models::ConnectionError;
15-
use crate::dbus::{NMDeviceProxy, NMProxy, NMWirelessProxy};
18+
use crate::dbus::{NMAccessPointProxy, NMDeviceProxy, NMProxy, NMWirelessProxy};
1619
use crate::types::constants::device_type;
1720

21+
type NetworkChangeStream = Pin<Box<dyn Stream<Item = NetworkChange> + Send>>;
22+
23+
enum NetworkChange {
24+
Added(OwnedObjectPath),
25+
Removed(OwnedObjectPath),
26+
SignalStrengthChanged,
27+
}
28+
1829
/// Monitors access point changes on all Wi-Fi devices.
1930
///
2031
/// Subscribes to `AccessPointAdded` and `AccessPointRemoved` signals on all
21-
/// wireless devices. When any signal is received, invokes the callback to
22-
/// notify the caller that the network list has changed.
32+
/// wireless devices, plus `Strength` property changes on visible access points.
33+
/// When any signal is received, invokes the callback to notify the caller that
34+
/// the network list or signal data has changed.
2335
///
2436
/// This function runs indefinitely until an error occurs or the connection
2537
/// is lost. Run it in a background task.
@@ -44,7 +56,8 @@ where
4456
let devices = nm.get_devices().await?;
4557

4658
// Use dynamic dispatch to handle different signal stream types
47-
let mut streams: Vec<Pin<Box<dyn Stream<Item = _> + Send>>> = Vec::new();
59+
let mut streams: Vec<NetworkChangeStream> = Vec::new();
60+
let mut monitored_access_points = HashSet::new();
4861

4962
// Subscribe to signals on all Wi-Fi devices
5063
for dev_path in devices {
@@ -65,11 +78,45 @@ where
6578
let added_stream = wifi.receive_access_point_added().await?;
6679
let removed_stream = wifi.receive_access_point_removed().await?;
6780

68-
// Box both streams as trait objects
69-
streams.push(Box::pin(added_stream.map(|_| ())));
70-
streams.push(Box::pin(removed_stream.map(|_| ())));
81+
streams.push(Box::pin(added_stream.map(|signal| {
82+
signal.args().map_or_else(
83+
|err| {
84+
debug!("Failed to parse AccessPointAdded signal: {err}");
85+
NetworkChange::SignalStrengthChanged
86+
},
87+
|args| NetworkChange::Added(args.path().clone()),
88+
)
89+
})));
90+
streams.push(Box::pin(removed_stream.map(|signal| {
91+
signal.args().map_or_else(
92+
|err| {
93+
debug!("Failed to parse AccessPointRemoved signal: {err}");
94+
NetworkChange::SignalStrengthChanged
95+
},
96+
|args| NetworkChange::Removed(args.path().clone()),
97+
)
98+
})));
99+
100+
match wifi.access_points().await {
101+
Ok(ap_paths) => {
102+
for ap_path in ap_paths {
103+
if !monitored_access_points.insert(ap_path.to_string()) {
104+
continue;
105+
}
106+
107+
match access_point_strength_stream(conn, ap_path.clone()).await {
108+
Ok(stream) => streams.push(stream),
109+
Err(err) => debug!(
110+
"Failed to monitor signal strength for access point {}: {}",
111+
ap_path, err
112+
),
113+
}
114+
}
115+
}
116+
Err(err) => debug!("Failed to list access points on device {dev_path}: {err}"),
117+
}
71118

72-
debug!("Subscribed to AP signals on device: {dev_path}");
119+
debug!("Subscribed to network change signals on device: {dev_path}");
73120
}
74121

75122
if streams.is_empty() {
@@ -93,7 +140,23 @@ where
93140
}
94141
signal = merged.next() => {
95142
match signal {
96-
Some(_) => callback(),
143+
Some(NetworkChange::Added(path)) => {
144+
if monitored_access_points.insert(path.to_string()) {
145+
match access_point_strength_stream(conn, path.clone()).await {
146+
Ok(stream) => merged.push(stream),
147+
Err(err) => debug!(
148+
"Failed to monitor signal strength for access point {}: {}",
149+
path, err
150+
),
151+
}
152+
}
153+
callback();
154+
}
155+
Some(NetworkChange::Removed(path)) => {
156+
monitored_access_points.remove(path.as_str());
157+
callback();
158+
}
159+
Some(NetworkChange::SignalStrengthChanged) => callback(),
97160
None => break,
98161
}
99162
}
@@ -107,3 +170,20 @@ where
107170

108171
Err(ConnectionError::Stuck("monitoring stream ended".into()))
109172
}
173+
174+
async fn access_point_strength_stream(
175+
conn: &Connection,
176+
ap_path: OwnedObjectPath,
177+
) -> Result<NetworkChangeStream> {
178+
let ap = NMAccessPointProxy::builder(conn)
179+
.path(ap_path.clone())?
180+
.build()
181+
.await?;
182+
183+
let stream = ap.receive_strength_changed().await.skip(1).map(move |_| {
184+
debug!("Access point signal strength changed: {ap_path}");
185+
NetworkChange::SignalStrengthChanged
186+
});
187+
188+
Ok(Box::pin(stream))
189+
}

0 commit comments

Comments
 (0)