diff --git a/nmrs-gui/CHANGELOG.md b/nmrs-gui/CHANGELOG.md index ad68d82a..36e9a830 100644 --- a/nmrs-gui/CHANGELOG.md +++ b/nmrs-gui/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to the `nmrs-gui` crate will be documented in this file. ## [Unreleased] +### Changed +- Use `Arc` for monitor callbacks to satisfy `Send` bound ([#359](https://github.com/cachebag/nmrs/pull/359)) ## [1.5.1] - 2026-04-10 ### Fixed diff --git a/nmrs-gui/src/ui/mod.rs b/nmrs-gui/src/ui/mod.rs index 280a0168..f4f7384d 100644 --- a/nmrs-gui/src/ui/mod.rs +++ b/nmrs-gui/src/ui/mod.rs @@ -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; @@ -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; @@ -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; @@ -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) => { diff --git a/nmrs/CHANGELOG.md b/nmrs/CHANGELOG.md index da898024..1c29e985 100644 --- a/nmrs/CHANGELOG.md +++ b/nmrs/CHANGELOG.md @@ -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)) diff --git a/nmrs/src/api/network_manager.rs b/nmrs/src/api/network_manager.rs index daac7032..de22b056 100644 --- a/nmrs/src/api/network_manager.rs +++ b/nmrs/src/api/network_manager.rs @@ -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(&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 @@ -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(&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 diff --git a/nmrs/src/monitoring/device.rs b/nmrs/src/monitoring/device.rs index a7765951..3807ef73 100644 --- a/nmrs/src/monitoring/device.rs +++ b/nmrs/src/monitoring/device.rs @@ -38,12 +38,12 @@ pub async fn monitor_device_changes( 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>>> = Vec::new(); + let mut streams: Vec + Send>>> = Vec::new(); // Subscribe to DeviceAdded and DeviceRemoved signals from main NetworkManager // This is more reliable than subscribing to individual devices diff --git a/nmrs/src/monitoring/network.rs b/nmrs/src/monitoring/network.rs index 1dcb14c9..84ce776e 100644 --- a/nmrs/src/monitoring/network.rs +++ b/nmrs/src/monitoring/network.rs @@ -38,13 +38,13 @@ pub async fn monitor_network_changes( 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>>> = Vec::new(); + let mut streams: Vec + Send>>> = Vec::new(); // Subscribe to signals on all Wi-Fi devices for dev_path in devices {