Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nmrs-gui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
All notable changes to the `nmrs-gui` crate will be documented in this file.

## [Unreleased]
### Changed
- Use `Arc<Notify>` for monitor callbacks to satisfy `Send` bound ([#359](https://github.com/cachebag/nmrs/pull/359))

## [1.5.1] - 2026-04-10
### Fixed
Expand Down
124 changes: 54 additions & 70 deletions nmrs-gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use gtk::{
};
use std::cell::Cell;
use std::rc::Rc;
use std::sync::Arc;
use tokio::sync::Notify;

use crate::ui::header::THEMES;

Expand Down Expand Up @@ -164,46 +166,15 @@ pub fn build_ui(app: &Application) {

{
let nm_device_monitor = nm.clone();
let list_container_device = list_container_clone.clone();
let is_scanning_device = is_scanning_clone.clone();
let ctx_device = ctx.clone();
let pending_device_refresh = Rc::new(std::cell::RefCell::new(false));
let device_notify = Arc::new(Notify::new());

let notify_clone = device_notify.clone();
glib::MainContext::default().spawn_local(async move {
loop {
let ctx_device_clone = ctx_device.clone();
let list_container_clone = list_container_device.clone();
let is_scanning_clone = is_scanning_device.clone();
let pending_device_refresh_clone = pending_device_refresh.clone();

let notify = notify_clone.clone();
let result = nm_device_monitor
.monitor_device_changes(move || {
let ctx = ctx_device_clone.clone();
let list_container = list_container_clone.clone();
let is_scanning = is_scanning_clone.clone();
let pending_refresh = pending_device_refresh_clone.clone();

if pending_refresh.replace(true) {
return;
}

glib::MainContext::default().spawn_local(async move {
glib::timeout_future_seconds(3).await;
*pending_refresh.borrow_mut() = false;

let current_page = ctx.stack.visible_child_name();
let on_networks_page =
current_page.as_deref() == Some("networks");

if !is_scanning.get() && on_networks_page {
header::refresh_networks_no_scan(
ctx,
&list_container,
&is_scanning,
)
.await;
}
});
notify.notify_one();
})
.await;

Expand All @@ -213,50 +184,41 @@ pub fn build_ui(app: &Application) {
glib::timeout_future_seconds(5).await;
}
});

let list_container_device = list_container_clone.clone();
let is_scanning_device = is_scanning_clone.clone();
let ctx_device = ctx.clone();
glib::MainContext::default().spawn_local(async move {
loop {
device_notify.notified().await;
glib::timeout_future_seconds(3).await;

let current_page = ctx_device.stack.visible_child_name();
let on_networks_page = current_page.as_deref() == Some("networks");

if !is_scanning_device.get() && on_networks_page {
header::refresh_networks_no_scan(
ctx_device.clone(),
&list_container_device,
&is_scanning_device,
)
.await;
}
}
});
}

{
let nm_network_monitor = nm.clone();
let list_container_network = list_container_clone.clone();
let is_scanning_network = is_scanning_clone.clone();
let ctx_network = ctx.clone();
let pending_network_refresh = Rc::new(std::cell::RefCell::new(false));
let network_notify = Arc::new(Notify::new());

let notify_clone = network_notify.clone();
glib::MainContext::default().spawn_local(async move {
loop {
let ctx_network_clone = ctx_network.clone();
let list_container_clone = list_container_network.clone();
let is_scanning_clone = is_scanning_network.clone();
let pending_network_refresh_clone = pending_network_refresh.clone();

let notify = notify_clone.clone();
let result = nm_network_monitor
.monitor_network_changes(move || {
let ctx = ctx_network_clone.clone();
let list_container = list_container_clone.clone();
let is_scanning = is_scanning_clone.clone();
let pending_refresh = pending_network_refresh_clone.clone();

if pending_refresh.replace(true) {
return;
}

glib::MainContext::default().spawn_local(async move {
glib::timeout_future_seconds(8).await;
*pending_refresh.borrow_mut() = false;

let current_page = ctx.stack.visible_child_name();
let on_networks_page =
current_page.as_deref() == Some("networks");

if !is_scanning.get() && on_networks_page {
header::refresh_networks_no_scan(
ctx,
&list_container,
&is_scanning,
)
.await;
}
});
notify.notify_one();
})
.await;

Expand All @@ -266,6 +228,28 @@ pub fn build_ui(app: &Application) {
glib::timeout_future_seconds(5).await;
}
});

let list_container_network = list_container_clone.clone();
let is_scanning_network = is_scanning_clone.clone();
let ctx_network = ctx.clone();
glib::MainContext::default().spawn_local(async move {
loop {
network_notify.notified().await;
glib::timeout_future_seconds(8).await;

let current_page = ctx_network.stack.visible_child_name();
let on_networks_page = current_page.as_deref() == Some("networks");

if !is_scanning_network.get() && on_networks_page {
header::refresh_networks_no_scan(
ctx_network.clone(),
&list_container_network,
&is_scanning_network,
)
.await;
}
}
});
}
}
Err(err) => {
Expand Down
1 change: 1 addition & 0 deletions nmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to the `nmrs` crate will be documented in this file.
- Support for specifying Bluetooth adapter in `BluetoothIdentity` ([#267](https://github.com/cachebag/nmrs/pull/267))

### Fixed
- 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))
- Line-accurate source locations for `.ovpn` directives and blocks ([#318](https://github.com/cachebag/nmrs/pull/318))
- `key_direction` when nested under `tls_auth` and as a standalone directive ([#320](https://github.com/cachebag/nmrs/pull/320))

Expand Down
30 changes: 12 additions & 18 deletions nmrs/src/api/network_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,21 +646,18 @@ impl NetworkManager {
/// # async fn example() -> nmrs::Result<()> {
/// let nm = NetworkManager::new().await?;
///
/// // Spawn monitoring task
/// glib::MainContext::default().spawn_local({
/// let nm = nm.clone();
/// async move {
/// nm.monitor_network_changes(|| {
/// println!("Networks changed!");
/// }).await
/// }
/// let nm_clone = nm.clone();
/// tokio::spawn(async move {
/// nm_clone.monitor_network_changes(|| {
/// println!("Networks changed!");
/// }).await
/// });
/// # Ok(())
/// # }
/// ```
pub async fn monitor_network_changes<F>(&self, callback: F) -> Result<()>
where
F: Fn() + 'static,
F: Fn() + Send + 'static,
{
let (_tx, rx) = watch::channel(());
network_monitor::monitor_network_changes(&self.conn, rx, callback).await
Expand All @@ -683,21 +680,18 @@ impl NetworkManager {
/// # async fn example() -> nmrs::Result<()> {
/// let nm = NetworkManager::new().await?;
///
/// // Spawn monitoring task
/// glib::MainContext::default().spawn_local({
/// let nm = nm.clone();
/// async move {
/// nm.monitor_device_changes(|| {
/// println!("Device state changed!");
/// }).await
/// }
/// let nm_clone = nm.clone();
/// tokio::spawn(async move {
/// nm_clone.monitor_device_changes(|| {
/// println!("Device state changed!");
/// }).await
/// });
/// # Ok(())
/// # }
/// ```
pub async fn monitor_device_changes<F>(&self, callback: F) -> Result<()>
where
F: Fn() + 'static,
F: Fn() + Send + 'static,
{
let (_tx, rx) = watch::channel(());
device_monitor::monitor_device_changes(&self.conn, rx, callback).await
Expand Down
4 changes: 2 additions & 2 deletions nmrs/src/monitoring/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ pub async fn monitor_device_changes<F>(
callback: F,
) -> Result<()>
where
F: Fn() + 'static,
F: Fn() + Send + 'static,
{
let nm = NMProxy::new(conn).await?;

// Use dynamic dispatch to handle different signal stream types
let mut streams: Vec<Pin<Box<dyn Stream<Item = _>>>> = Vec::new();
let mut streams: Vec<Pin<Box<dyn Stream<Item = _> + Send>>> = Vec::new();

// Subscribe to DeviceAdded and DeviceRemoved signals from main NetworkManager
// This is more reliable than subscribing to individual devices
Expand Down
4 changes: 2 additions & 2 deletions nmrs/src/monitoring/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ pub async fn monitor_network_changes<F>(
callback: F,
) -> Result<()>
where
F: Fn() + 'static,
F: Fn() + Send + 'static,
{
let nm = NMProxy::new(conn).await?;
let devices = nm.get_devices().await?;

// Use dynamic dispatch to handle different signal stream types
let mut streams: Vec<Pin<Box<dyn Stream<Item = _>>>> = Vec::new();
let mut streams: Vec<Pin<Box<dyn Stream<Item = _> + Send>>> = Vec::new();

// Subscribe to signals on all Wi-Fi devices
for dev_path in devices {
Expand Down