diff --git a/Cargo.lock b/Cargo.lock index 1a7d46bd..53b3ca25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,9 +223,12 @@ name = "cosmic-applet-network" version = "0.1.0" dependencies = [ "cosmic-dbus-networkmanager", + "futures-util", "gtk4", + "itertools", "once_cell", "relm4-macros", + "slotmap", "tokio", "zbus", ] @@ -338,6 +341,12 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "enumflags2" version = "0.7.3" @@ -848,6 +857,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "libc" version = "0.2.116" @@ -1374,6 +1392,15 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.8.0" diff --git a/applets/cosmic-applet-network/Cargo.toml b/applets/cosmic-applet-network/Cargo.toml index ab0bdef7..5ce627ec 100644 --- a/applets/cosmic-applet-network/Cargo.toml +++ b/applets/cosmic-applet-network/Cargo.toml @@ -6,8 +6,11 @@ license = "LGPL-3.0-or-later" [dependencies] cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings" } +futures-util = "0.3.19" gtk4 = "0.4.6" +itertools = "0.10.3" once_cell = "1.9.0" relm4-macros = "0.4.1" +slotmap = "1.0.6" tokio = { version = "1.15.0", features = ["full"] } zbus = "2.0.1" diff --git a/applets/cosmic-applet-network/src/ui/available_wifi.rs b/applets/cosmic-applet-network/src/ui/available_wifi.rs index 1c75f022..b9566459 100644 --- a/applets/cosmic-applet-network/src/ui/available_wifi.rs +++ b/applets/cosmic-applet-network/src/ui/available_wifi.rs @@ -1,5 +1,135 @@ // SPDX-License-Identifier: LGPL-3.0-or-later -use gtk4::{Label, ScrolledWindow}; +use cosmic_dbus_networkmanager::{ + device::SpecificDevice, interface::enums::ApSecurityFlags, nm::NetworkManager, +}; +use futures_util::{StreamExt, TryFutureExt}; +use gtk4::{ + glib, prelude::*, Align, Button, Dialog, HeaderBar, Image, Label, Orientation, ScrolledWindow, + Spinner, +}; +use itertools::Itertools; +use slotmap::{DefaultKey, SlotMap}; +use std::{ + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use tokio::sync::mpsc::UnboundedSender; +use zbus::Connection; pub fn add_available_wifi(target: >k4::Box) {} + +async fn scan_for_devices(tx: UnboundedSender>) { + let sys_conn = match Connection::system().await { + Ok(conn) => conn, + Err(err) => { + //error!(%err, "Failed to connect to system dbus session"); + return; + } + }; + let nm = match NetworkManager::new(&sys_conn).await { + Ok(p) => p, + Err(err) => { + //error!(%err, "Failed to set up connection to NetworkManager dbus"); + return; + } + }; + let devices = match nm.devices().await { + Ok(d) => d, + Err(err) => { + //error!(%err, "Failed to get devices from NetworkManager"); + return; + } + }; + let mut all_aps = SlotMap::new(); + + for d in devices { + if let Ok(Some(SpecificDevice::Wireless(w))) = d.downcast_to_device().await { + let id = d + .active_connection() + .and_then(|ac| async move { ac.id().await }) + .await + .unwrap_or_else(|_| "unknown".to_string()); + if let Err(err) = w.request_scan(std::collections::HashMap::new()).await { + //error!(%err, %id, "Wi-Fi scan failed"); + continue; + }; + let mut scan_changed = w.receive_last_scan_changed().await; + if let Some(t) = scan_changed.next().await { + if let Ok(t) = t.get().await { + if t == -1 { + //error!(%id, "Getting access point failed"); + continue; + } + } + match w.get_access_points().await { + Ok(aps) => { + if !aps.is_empty() { + for ap in AccessPoint::from_list(aps).await { + all_aps.insert(ap); + } + + break; + } + } + Err(err) => { + //error!(%err, %id, "Getting access points failed"); + continue; + } + }; + } + } + } + + if let Err(err) = tx.send(all_aps) { + //error!(%err, "failed to send AP list"); + } +} + +#[derive(Debug)] +pub struct AccessPoint { + pub ssid: String, + pub hw_address: String, + pub strength: u8, + pub wpa_flags: ApSecurityFlags, +} + +impl AccessPoint { + pub async fn new( + ap: cosmic_dbus_networkmanager::access_point::AccessPoint<'_>, + ) -> Option { + Some(Self { + ssid: ap + .ssid() + .await + .map(|x| String::from_utf8_lossy(&x).into_owned()) + .ok()?, + hw_address: ap.hw_address().await.ok()?, + strength: ap.strength().await.ok()?, + wpa_flags: ap.wpa_flags().await.ok()?, + }) + } + + pub async fn from_list( + aps: Vec>, + ) -> Vec { + let mut out = Vec::::with_capacity(aps.len()); + for ap in aps { + if let Some(ap) = Self::new(ap).await { + out.push(ap); + } + } + let mut ret = out + .into_iter() + .sorted_by(|a, b| a.strength.cmp(&b.strength)) + .rev() + .unique_by(|ap| ap.ssid.clone()) + .collect::>(); + // for some reason adding .rev() messes up unique_by, so we do this instead + ret.reverse(); + ret + } +}