diff --git a/cosmic-applet-network/src/app.rs b/cosmic-applet-network/src/app.rs index a1761f4f..ce8c7434 100644 --- a/cosmic-applet-network/src/app.rs +++ b/cosmic-applet-network/src/app.rs @@ -34,9 +34,13 @@ use zbus::Connection; use crate::{ config, fl, network_manager::{ - active_conns::active_conns_subscription, available_wifi::AccessPoint, - current_networks::ActiveConnectionInfo, devices::devices_subscription, - network_manager_subscription, wireless_enabled::wireless_enabled_subscription, + active_conns::active_conns_subscription, + available_wifi::AccessPoint, + current_networks::ActiveConnectionInfo, + devices::devices_subscription, + hw_address::{self, HwAddress}, + network_manager_subscription, + wireless_enabled::wireless_enabled_subscription, NetworkManagerEvent, NetworkManagerRequest, NetworkManagerState, }, }; @@ -67,6 +71,17 @@ impl NewConnectionState { } .ssid } + pub fn hw_address(&self) -> HwAddress { + match self { + Self::EnterPassword { + access_point, + password: _, + } => access_point, + Self::Waiting(ap) => ap, + Self::Failure(ap) => ap, + } + .hw_address + } } impl From for AccessPoint { @@ -100,6 +115,7 @@ struct CosmicNetworkApplet { toggle_wifi_ctr: u128, token_tx: Option>, failed_known_ssids: HashSet, + hw_device_to_show: Option, } fn wifi_icon(strength: u8) -> &'static str { @@ -201,12 +217,28 @@ impl CosmicNetworkApplet { timeline.start(); } } + fn view_window_return<'a>(&self, mut content: Column<'a, Message>) -> Element<'a, Message> { + let Spacing { + space_xxs, space_s, .. + } = theme::active().cosmic().spacing; + + content = content + .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])) + .push(menu_button(text::body(fl!("settings"))).on_press(Message::OpenSettings)); + + self.core + .applet + .popup_container(content.padding([8, 0, 8, 0])) + .max_width(400.) + .max_height(800.) + .into() + } } #[derive(Debug, Clone)] pub(crate) enum Message { - ActivateKnownWifi(String), - Disconnect(String), + ActivateKnownWifi(String, HwAddress), + Disconnect(String, HwAddress), TogglePopup, CloseRequested(window::Id), ToggleAirplaneMode(bool), @@ -220,7 +252,8 @@ pub(crate) enum Message { Frame(Instant), Token(TokenUpdate), OpenSettings, - ResetFailedKnownSsid(String), + ResetFailedKnownSsid(String, HwAddress), + OpenHwDevice(Option), // Errored(String), } @@ -316,15 +349,15 @@ impl cosmic::Application for CosmicNetworkApplet { success, req, } => { - if let NetworkManagerRequest::SelectAccessPoint(ssid) = &req { + if let NetworkManagerRequest::SelectAccessPoint(ssid, hw_address) = &req { let conn_match = self .new_connection .as_ref() - .map(|c| c.ssid() == ssid) + .map(|c| c.ssid() == ssid && c.hw_address() == *hw_address) .unwrap_or_default(); if conn_match && success { if let Some(s) = - state.active_conns.iter_mut().find(|ap| &ap.name() == ssid) + state.active_conns.iter_mut().find(|ap| &ap.name() == ssid && ap.hw_address() == *hw_address) { match s { ActiveConnectionInfo::WiFi { state, .. } => { @@ -343,11 +376,11 @@ impl cosmic::Application for CosmicNetworkApplet { { self.failed_known_ssids.insert(ssid.clone()); } - } else if let NetworkManagerRequest::Password(ssid, _) = &req { + } else if let NetworkManagerRequest::Password(ssid, _, hw_address) = &req { if let Some(NewConnectionState::Waiting(access_point)) = self.new_connection.clone() { - if !success && ssid == &access_point.ssid { + if !success && ssid == &access_point.ssid && *hw_address == access_point.hw_address { self.new_connection = Some(NewConnectionState::Failure(access_point.clone())); } else { @@ -358,7 +391,7 @@ impl cosmic::Application for CosmicNetworkApplet { access_point, .. }) = self.new_connection.clone() { - if success && ssid == &access_point.ssid { + if success && ssid == &access_point.ssid && *hw_address == access_point.hw_address{ self.new_connection = None; self.show_visible_networks = false; } @@ -396,6 +429,7 @@ impl cosmic::Application for CosmicNetworkApplet { let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint( access_point.ssid.clone(), + access_point.hw_address.clone(), )); self.new_connection = Some(NewConnectionState::EnterPassword { @@ -430,18 +464,19 @@ impl cosmic::Application for CosmicNetworkApplet { let _ = tx.unbounded_send(NetworkManagerRequest::Password( access_point.ssid.clone(), password, + access_point.hw_address, )); self.new_connection .replace(NewConnectionState::Waiting(access_point)); }; } - Message::ActivateKnownWifi(ssid) => { + Message::ActivateKnownWifi(ssid, hw_address) => { let tx = if let Some(tx) = self.nm_sender.as_ref() { if let Some(ap) = self .nm_state .known_access_points .iter_mut() - .find(|c| c.ssid == ssid) + .find(|c| c.ssid == ssid && c.hw_address == hw_address) { ap.working = true; } @@ -449,19 +484,20 @@ impl cosmic::Application for CosmicNetworkApplet { } else { return Task::none(); }; - let _ = tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint(ssid)); + let _ = + tx.unbounded_send(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address)); } Message::CancelNewConnection => { self.new_connection = None; } - Message::Disconnect(ssid) => { + Message::Disconnect(ssid, hw_address) => { self.new_connection = None; let tx = if let Some(tx) = self.nm_sender.as_ref() { if let Some(ActiveConnectionInfo::WiFi { state, .. }) = self .nm_state .active_conns .iter_mut() - .find(|c| c.name() == ssid) + .find(|c| c.name() == ssid && c.hw_address() == hw_address) { *state = ActiveConnectionState::Deactivating; } @@ -472,6 +508,7 @@ impl cosmic::Application for CosmicNetworkApplet { let _ = tx.unbounded_send(NetworkManagerRequest::Disconnect(ssid)); } Message::CloseRequested(id) => { + self.hw_device_to_show = None; if Some(id) == self.popup { self.popup = None; } @@ -502,24 +539,27 @@ impl cosmic::Application for CosmicNetworkApplet { tokio::spawn(cosmic::process::spawn(cmd)); } }, - Message::ResetFailedKnownSsid(ssid) => { + Message::OpenHwDevice(hw_address) => { + self.hw_device_to_show = hw_address; + } + Message::ResetFailedKnownSsid(ssid, hw_address) => { let ap = if let Some(pos) = self .nm_state .known_access_points .iter() - .position(|ap| ap.ssid == ssid) + .position(|ap| ap.ssid == ssid && ap.hw_address == hw_address) { self.nm_state.known_access_points.remove(pos) } else if let Some((pos, ap)) = self .nm_state .active_conns .iter() - .position(|conn| conn.name() == ssid) + .position(|conn| conn.name() == ssid && conn.hw_address() == hw_address) .zip( self.nm_state .wireless_access_points .iter() - .find(|ap| ap.ssid == ssid), + .find(|ap| ap.ssid == ssid && ap.hw_address == hw_address), ) { self.nm_state.active_conns.remove(pos); @@ -556,6 +596,9 @@ impl cosmic::Application for CosmicNetworkApplet { for conn in &self.nm_state.active_conns { match conn { ActiveConnectionInfo::Vpn { name, ip_addresses } => { + if self.hw_device_to_show.is_some() { + continue; + } let mut ipv4 = Vec::with_capacity(ip_addresses.len() + 1); ipv4.push(text::body(name).into()); for addr in ip_addresses { @@ -583,10 +626,15 @@ impl cosmic::Application for CosmicNetworkApplet { } ActiveConnectionInfo::Wired { name, - hw_address: _, + hw_address, speed, ip_addresses, } => { + if self.hw_device_to_show.is_some() + && *hw_address != self.hw_device_to_show.unwrap() + { + continue; + } let mut ipv4 = Vec::with_capacity(ip_addresses.len() + 1); ipv4.push(text::body(name).into()); for addr in ip_addresses { @@ -622,8 +670,13 @@ impl cosmic::Application for CosmicNetworkApplet { ip_addresses, state, strength, - .. + hw_address, } => { + if self.hw_device_to_show.is_some() + && hw_address != self.hw_device_to_show.as_ref().unwrap() + { + continue; + } let mut ipv4 = Vec::with_capacity(ip_addresses.len()); for addr in ip_addresses { ipv4.push(text(format!("{}: {}", fl!("ipv4"), addr)).size(12).into()); @@ -660,7 +713,7 @@ impl cosmic::Application for CosmicNetworkApplet { from_name("view-refresh-symbolic").size(16), ) .icon_size(16) - .on_press(Message::ResetFailedKnownSsid(name.clone())) + .on_press(Message::ResetFailedKnownSsid(name.clone(), *hw_address)) .into(), ) } @@ -671,42 +724,65 @@ impl cosmic::Application for CosmicNetworkApplet { .align_y(Alignment::Center) .spacing(8) ) - .on_press(Message::Disconnect(name.clone()))] + .on_press(Message::Disconnect(name.clone(), *hw_address))] .align_x(Alignment::Center), )); } }; } - let mut content = column![ - vpn_ethernet_col, - padded_control( - anim!( - //toggler - AIRPLANE_MODE, - &self.timeline, - fl!("airplane-mode"), - self.nm_state.airplane_mode, - |_chain, enable| { Message::ToggleAirplaneMode(enable) }, - ) - .text_size(14) - .width(Length::Fill) - ), - padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), - padded_control( - anim!( - //toggler - WIFI, - &self.timeline, - fl!("wifi"), - self.nm_state.wifi_enabled, - |_chain, enable| { Message::ToggleWiFi(enable) }, - ) - .text_size(14) - .width(Length::Fill) - ), - ] - .align_x(Alignment::Center); + let mut content = if let Some(hw_device_to_show) = self.hw_device_to_show.as_ref() { + column![ + vpn_ethernet_col, + menu_button(row![ + container( + icon::from_name("go-previous-symbolic") + .size(16) + .symbolic(true) + ) + .align_x(Alignment::Start) + .align_y(Alignment::Center) + .width(Length::Fixed(24.0)) + .height(Length::Fixed(24.0)), + text::body(hw_device_to_show.to_string()) + .width(Length::Fill) + .height(Length::Fixed(24.0)) + .align_y(Alignment::Center), + ]) + .on_press(Message::OpenHwDevice(None)) + ] + } else { + column![ + vpn_ethernet_col, + padded_control( + anim!( + //toggler + AIRPLANE_MODE, + &self.timeline, + fl!("airplane-mode"), + self.nm_state.airplane_mode, + |_chain, enable| { Message::ToggleAirplaneMode(enable) }, + ) + .text_size(14) + .width(Length::Fill) + ), + padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), + padded_control( + anim!( + //toggler + WIFI, + &self.timeline, + fl!("wifi"), + self.nm_state.wifi_enabled, + |_chain, enable| { Message::ToggleWiFi(enable) }, + ) + .text_size(14) + .width(Length::Fill) + ), + ] + .align_x(Alignment::Center) + }; + if self.nm_state.airplane_mode { content = content.push( column!( @@ -722,237 +798,286 @@ impl cosmic::Application for CosmicNetworkApplet { .align_x(Alignment::Center) .width(Length::Fill), ); - } else { - if self.nm_state.wifi_enabled { - content = content.push( - padded_control(divider::horizontal::default()).padding([space_xxs, space_s]), - ); - for known in &self.nm_state.known_access_points { - let mut btn_content = Vec::with_capacity(2); - let ssid = text::body(&known.ssid).width(Length::Fill); - if known.working { - btn_content.push( - icon::from_name("network-wireless-acquiring-symbolic") - .size(24) - .symbolic(true) - .into(), - ); - btn_content.push(ssid.into()); - btn_content.push( - icon::from_name("process-working-symbolic") - .size(24) - .symbolic(true) - .into(), - ); - } else if matches!(known.state, DeviceState::Unavailable) { - btn_content.push( - icon::from_name("network-wireless-disconnected-symbolic") - .size(24) - .symbolic(true) - .into(), - ); - btn_content.push(ssid.into()); - } else { - btn_content.push( - icon::from_name(wifi_icon(known.strength)) - .size(24) - .symbolic(true) - .into(), - ); - btn_content.push(ssid.into()); - } + return self.view_window_return(content); + } - if self.failed_known_ssids.contains(&known.ssid) { - btn_content.push( - cosmic::widget::button::icon( - from_name("view-refresh-symbolic").size(16), - ) - .icon_size(16) - .on_press(Message::ResetFailedKnownSsid(known.ssid.clone())) - .into(), - ) - } + if !self.nm_state.wifi_enabled { + return self.view_window_return(content); + } - let mut btn = menu_button( + content = content + .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])); + + let wireless_hw_devices = self + .nm_state + .wireless_access_points + .iter() + .map(|ap| ap.hw_address) + .collect::>(); + + if wireless_hw_devices.len() > 1 && self.hw_device_to_show.is_none() { + for hw_device in wireless_hw_devices { + let display_name = hw_device.to_string(); + + let btn_content = vec![ + column![ + text::body(display_name), + Column::with_children(vec![text("Adapter").size(10).into()]) + ] + .width(Length::Fill) + .into(), + icon::from_name("go-next-symbolic") + .size(16) + .symbolic(true) + .into(), + ]; + content = content.push(Element::from( + column![menu_button( Row::with_children(btn_content) .align_y(Alignment::Center) - .spacing(8), - ); - btn = match known.state { - DeviceState::Failed - | DeviceState::Unknown - | DeviceState::Unmanaged - | DeviceState::Disconnected - | DeviceState::NeedAuth => { - btn.on_press(Message::ActivateKnownWifi(known.ssid.clone())) - } - DeviceState::Activated => { - btn.on_press(Message::Disconnect(known.ssid.clone())) - } - _ => btn, - }; - known_wifi.push(Element::from(row![btn].align_y(Alignment::Center))); - } - - let has_known_wifi = !known_wifi.is_empty(); - content = content.push(Column::with_children(known_wifi)); - if has_known_wifi { - content = content.push( - padded_control(divider::horizontal::default()) - .padding([space_xxs, space_s]), - ); - } - - let dropdown_icon = if self.show_visible_networks { - "go-up-symbolic" - } else { - "go-down-symbolic" - }; - let available_connections_btn = menu_button(row![ - text::body(fl!("visible-wireless-networks")) - .width(Length::Fill) - .height(Length::Fixed(24.0)) - .align_y(Alignment::Center), - container(icon::from_name(dropdown_icon).size(16).symbolic(true)) - .center(Length::Fixed(24.0)) - ]) - .on_press(Message::ToggleVisibleNetworks); - content = content.push(available_connections_btn); - } - } - if self.show_visible_networks { - if let Some(new_conn_state) = self.new_connection.as_ref() { - match new_conn_state { - NewConnectionState::EnterPassword { - access_point, - password, - } => { - let id = padded_control( - row![ - icon::from_name("network-wireless-acquiring-symbolic") - .size(24) - .symbolic(true), - text::body(&access_point.ssid), - ] - .align_y(Alignment::Center) - .spacing(12), - ); - content = content.push(id); - let col = padded_control( - column![ - text::body(fl!("enter-password")), - text_input("", password) - .on_input(Message::Password) - .on_paste(Message::Password) - .on_submit(Message::SubmitPassword) - .password(), - container(text::body(fl!("router-wps-button"))).padding(8), - row![ - button::standard(fl!("cancel")) - .on_press(Message::CancelNewConnection), - button::suggested(fl!("connect")) - .on_press(Message::SubmitPassword) - ] - .spacing(24) - ] .spacing(8) - .align_x(Alignment::Center), - ) - .align_x(Alignment::Center); - content = content.push(col); - } - NewConnectionState::Waiting(access_point) => { - let id = row![ + ) + .on_press(Message::OpenHwDevice(Some(hw_device.clone())))] + .align_x(Alignment::Center), + )); + } + + return self.view_window_return(content); + } + + for known in &self.nm_state.known_access_points { + if let Some(filter_hw_address) = self.hw_device_to_show.as_ref() { + if filter_hw_address != &known.hw_address { + continue; + } + } + let mut btn_content = Vec::with_capacity(2); + let ssid = text::body(&known.ssid).width(Length::Fill); + if known.working { + btn_content.push( + icon::from_name("network-wireless-acquiring-symbolic") + .size(24) + .symbolic(true) + .into(), + ); + btn_content.push(ssid.into()); + btn_content.push( + icon::from_name("process-working-symbolic") + .size(24) + .symbolic(true) + .into(), + ); + } else if matches!(known.state, DeviceState::Unavailable) { + btn_content.push( + icon::from_name("network-wireless-disconnected-symbolic") + .size(24) + .symbolic(true) + .into(), + ); + btn_content.push(ssid.into()); + } else { + btn_content.push( + icon::from_name(wifi_icon(known.strength)) + .size(24) + .symbolic(true) + .into(), + ); + btn_content.push(ssid.into()); + } + + if self.failed_known_ssids.contains(&known.ssid) { + btn_content.push( + cosmic::widget::button::icon(from_name("view-refresh-symbolic").size(16)) + .icon_size(16) + .on_press(Message::ResetFailedKnownSsid( + known.ssid.clone(), + known.hw_address, + )) + .into(), + ) + } + + let mut btn = menu_button( + Row::with_children(btn_content) + .align_y(Alignment::Center) + .spacing(8), + ); + btn = match known.state { + DeviceState::Failed + | DeviceState::Unknown + | DeviceState::Unmanaged + | DeviceState::Disconnected + | DeviceState::NeedAuth => btn.on_press(Message::ActivateKnownWifi( + known.ssid.clone(), + known.hw_address.clone(), + )), + DeviceState::Activated => { + btn.on_press(Message::Disconnect(known.ssid.clone(), known.hw_address)) + } + _ => btn, + }; + known_wifi.push(Element::from(row![btn].align_y(Alignment::Center))); + } + let has_known_wifi = !known_wifi.is_empty(); + content = content.push(Column::with_children(known_wifi)); + if has_known_wifi { + content = content + .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])); + } + + let dropdown_icon = if self.show_visible_networks { + "go-up-symbolic" + } else { + "go-down-symbolic" + }; + let available_connections_btn = menu_button(row![ + text::body(fl!("visible-wireless-networks")) + .width(Length::Fill) + .height(Length::Fixed(24.0)) + .align_y(Alignment::Center), + container(icon::from_name(dropdown_icon).size(16).symbolic(true)) + .center(Length::Fixed(24.0)) + ]) + .on_press(Message::ToggleVisibleNetworks); + content = content.push(available_connections_btn); + + if self.show_visible_networks { + return self.view_window_return(content); + } + + if let Some(new_conn_state) = self.new_connection.as_ref() { + match new_conn_state { + NewConnectionState::EnterPassword { + access_point, + password, + } => { + let id = padded_control( + row![ icon::from_name("network-wireless-acquiring-symbolic") .size(24) .symbolic(true), text::body(&access_point.ssid), ] .align_y(Alignment::Center) - .width(Length::Fill) - .spacing(12); - let connecting = padded_control( + .spacing(12), + ); + content = content.push(id); + let col = padded_control( + column![ + text::body(fl!("enter-password")), + text_input("", password) + .on_input(Message::Password) + .on_paste(Message::Password) + .on_submit(Message::SubmitPassword) + .password(), + container(text::body(fl!("router-wps-button"))).padding(8), row![ - id, - icon::from_name("process-working-symbolic") - .size(24) - .symbolic(true), + button::standard(fl!("cancel")) + .on_press(Message::CancelNewConnection), + button::suggested(fl!("connect")).on_press(Message::SubmitPassword) ] - .spacing(8), - ); - content = content.push(connecting); - } - NewConnectionState::Failure(access_point) => { - let id = padded_control( - row![ - icon::from_name("network-wireless-error-symbolic") - .size(24) - .symbolic(true), - text::body(&access_point.ssid), - ] - .align_y(Alignment::Center) - .spacing(12), - ) - .align_x(Alignment::Center); - content = content.push(id); - let col = padded_control( - column![ - text(fl!("unable-to-connect")), - text(fl!("check-wifi-connection")), - row![ - button::standard(fl!("cancel")) - .on_press(Message::CancelNewConnection), - button::suggested(fl!("connect")).on_press( - Message::SelectWirelessAccessPoint(access_point.clone()) - ) - ] - .spacing(24) - ] - .spacing(16) - .align_x(Alignment::Center), - ) - .align_x(Alignment::Center); - content = content.push(col); - } + .spacing(24) + ] + .spacing(8) + .align_x(Alignment::Center), + ) + .align_x(Alignment::Center); + content = content.push(col); } - } else if self.nm_state.wifi_enabled { - let mut list_col = Vec::with_capacity(self.nm_state.wireless_access_points.len()); - for ap in &self.nm_state.wireless_access_points { - if self - .nm_state - .active_conns - .iter() - .any(|a| ap.ssid == a.name()) - { - continue; - } - let button = menu_button( + NewConnectionState::Waiting(access_point) => { + let id = row![ + icon::from_name("network-wireless-acquiring-symbolic") + .size(24) + .symbolic(true), + text::body(&access_point.ssid), + ] + .align_y(Alignment::Center) + .width(Length::Fill) + .spacing(12); + let connecting = padded_control( row![ - icon::from_name(wifi_icon(ap.strength)) - .size(16) + id, + icon::from_name("process-working-symbolic") + .size(24) .symbolic(true), - text::body(&ap.ssid).align_y(Alignment::Center) + ] + .spacing(8), + ); + content = content.push(connecting); + } + NewConnectionState::Failure(access_point) => { + let id = padded_control( + row![ + icon::from_name("network-wireless-error-symbolic") + .size(24) + .symbolic(true), + text::body(&access_point.ssid), ] .align_y(Alignment::Center) .spacing(12), ) - .on_press(Message::SelectWirelessAccessPoint(ap.clone())); - list_col.push(button.into()); + .align_x(Alignment::Center); + content = content.push(id); + let col = padded_control( + column![ + text(fl!("unable-to-connect")), + text(fl!("check-wifi-connection")), + row![ + button::standard(fl!("cancel")) + .on_press(Message::CancelNewConnection), + button::suggested(fl!("connect")).on_press( + Message::SelectWirelessAccessPoint(access_point.clone()) + ) + ] + .spacing(24) + ] + .spacing(16) + .align_x(Alignment::Center), + ) + .align_x(Alignment::Center); + content = content.push(col); } - content = content - .push(scrollable(Column::with_children(list_col)).height(Length::Fixed(300.0))); } + } else if self.nm_state.wifi_enabled { + let mut list_col = Vec::with_capacity(self.nm_state.wireless_access_points.len()); + for ap in &self.nm_state.wireless_access_points { + if ap.hw_address != self.hw_device_to_show.unwrap_or(ap.hw_address) { + continue; + } + if self + .nm_state + .active_conns + .iter() + .any(|a| ap.ssid == a.name() && ap.hw_address == a.hw_address()) + { + continue; + } + if self + .nm_state + .known_access_points + .iter() + .any(|a| ap.ssid == a.ssid && ap.hw_address == a.hw_address) + { + continue; + } + let button = menu_button( + row![ + icon::from_name(wifi_icon(ap.strength)) + .size(16) + .symbolic(true), + text::body(&ap.ssid).align_y(Alignment::Center) + ] + .align_y(Alignment::Center) + .spacing(12), + ) + .on_press(Message::SelectWirelessAccessPoint(ap.clone())); + list_col.push(button.into()); + } + content = content + .push(scrollable(Column::with_children(list_col)).height(Length::Fixed(300.0))); } - content = content - .push(padded_control(divider::horizontal::default()).padding([space_xxs, space_s])) - .push(menu_button(text::body(fl!("settings"))).on_press(Message::OpenSettings)); - self.core - .applet - .popup_container(content.padding([8, 0, 8, 0])) - .max_width(400.) - .max_height(800.) - .into() + self.view_window_return(content) } fn subscription(&self) -> Subscription { diff --git a/cosmic-applet-network/src/network_manager/available_wifi.rs b/cosmic-applet-network/src/network_manager/available_wifi.rs index 80e5b8f7..2b979bbc 100644 --- a/cosmic-applet-network/src/network_manager/available_wifi.rs +++ b/cosmic-applet-network/src/network_manager/available_wifi.rs @@ -7,6 +7,8 @@ use itertools::Itertools; use std::collections::HashMap; use zbus::zvariant::ObjectPath; +use super::hw_address::HwAddress; + pub async fn handle_wireless_device( device: WirelessDevice<'_>, hw_address: Option, @@ -45,7 +47,10 @@ pub async fn handle_wireless_device( state, working: false, path: ap.inner().path().to_owned(), - hw_address: hw_address.as_ref().unwrap_or(&"".to_string()).clone(), + hw_address: hw_address + .as_ref() + .and_then(|str_addr| HwAddress::from_str(str_addr)) + .unwrap_or_default(), }, ); } @@ -63,5 +68,5 @@ pub struct AccessPoint { pub state: DeviceState, pub working: bool, pub path: ObjectPath<'static>, - pub hw_address: String, + pub hw_address: HwAddress, } diff --git a/cosmic-applet-network/src/network_manager/current_networks.rs b/cosmic-applet-network/src/network_manager/current_networks.rs index c6f7d5aa..7c3857a7 100644 --- a/cosmic-applet-network/src/network_manager/current_networks.rs +++ b/cosmic-applet-network/src/network_manager/current_networks.rs @@ -6,6 +6,8 @@ use cosmic_dbus_networkmanager::{ }; use std::net::Ipv4Addr; +use super::hw_address::HwAddress; + pub async fn active_connections( active_connections: Vec>, ) -> zbus::Result> { @@ -40,7 +42,8 @@ pub async fn active_connections( Some(SpecificDevice::Wired(wired_device)) => { info.push(ActiveConnectionInfo::Wired { name: connection.id().await?, - hw_address: wired_device.hw_address().await?, + hw_address: HwAddress::from_string(&wired_device.hw_address().await?) + .unwrap_or_default(), speed: wired_device.speed().await?, ip_addresses: addresses.clone(), }); @@ -50,7 +53,10 @@ pub async fn active_connections( info.push(ActiveConnectionInfo::WiFi { name: String::from_utf8_lossy(&access_point.ssid().await?).into_owned(), ip_addresses: addresses.clone(), - hw_address: wireless_device.hw_address().await?, + hw_address: HwAddress::from_string( + &wireless_device.hw_address().await?, + ) + .unwrap_or_default(), state, strength: access_point.strength().await.unwrap_or_default(), }); @@ -83,14 +89,14 @@ pub async fn active_connections( pub enum ActiveConnectionInfo { Wired { name: String, - hw_address: String, + hw_address: HwAddress, speed: u32, ip_addresses: Vec, }, WiFi { name: String, ip_addresses: Vec, - hw_address: String, + hw_address: HwAddress, state: ActiveConnectionState, strength: u8, }, @@ -108,4 +114,11 @@ impl ActiveConnectionInfo { Self::Vpn { name, .. } => name.clone(), } } + pub fn hw_address(&self) -> HwAddress { + match &self { + Self::Wired { hw_address, .. } => *hw_address, + Self::WiFi { hw_address, .. } => *hw_address, + Self::Vpn { .. } => HwAddress::default(), + } + } } diff --git a/cosmic-applet-network/src/network_manager/hw_address.rs b/cosmic-applet-network/src/network_manager/hw_address.rs new file mode 100644 index 00000000..c9ea95c1 --- /dev/null +++ b/cosmic-applet-network/src/network_manager/hw_address.rs @@ -0,0 +1,39 @@ +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug, PartialOrd, Ord)] +pub struct HwAddress { + address: u64, +} + +impl HwAddress { + pub fn from_str(arg: &str) -> Option { + let columnless_vec = arg.split(":").collect::>(); + if columnless_vec.len() * 3 - 1 != arg.len() { + return None; + } + for byte in &columnless_vec { + if byte.len() != 2 { + return None; + } + } + u64::from_str_radix(columnless_vec.join("").as_str(), 16) + .ok() + .and_then(|address| Some(HwAddress { address })) + } + pub fn from_string(arg: &String) -> Option { + HwAddress::from_str(arg.as_str()) + } + pub fn to_string(&self) -> String { + // return if self.address > 100000000000000 { + // "Intel Corp".to_string() + // } else { + // "TP-Link".to_string() + // }; + format!("{:#x}", self.address) + .trim_start_matches("0x") + .chars() + .collect::>() + .chunks(2) + .map(|chunk| chunk.iter().cloned().collect::()) + .collect::>() + .join(":") + } +} diff --git a/cosmic-applet-network/src/network_manager/mod.rs b/cosmic-applet-network/src/network_manager/mod.rs index cd26d156..3f61f850 100644 --- a/cosmic-applet-network/src/network_manager/mod.rs +++ b/cosmic-applet-network/src/network_manager/mod.rs @@ -2,6 +2,7 @@ pub mod active_conns; pub mod available_wifi; pub mod current_networks; pub mod devices; +pub mod hw_address; pub mod wireless_enabled; use std::{collections::HashMap, fmt::Debug, time::Duration}; @@ -24,6 +25,7 @@ use futures::{ channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, SinkExt, StreamExt, }; +use hw_address::HwAddress; use tokio::process::Command; use zbus::{ zvariant::{self, Value}, @@ -172,15 +174,19 @@ async fn start_listening( }; _ = output.send(response).await; } - Some(NetworkManagerRequest::Password(ssid, password)) => { + Some(NetworkManagerRequest::Password(ssid, password, hw_address)) => { let nm_state = NetworkManagerState::new(&conn).await.unwrap_or_default(); let success = nm_state - .connect_wifi(&conn, &ssid, Some(&password)) + .connect_wifi(&conn, &ssid, Some(&password), hw_address) .await .is_ok(); let status = Some(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Password(ssid.clone(), password.clone()), + req: NetworkManagerRequest::Password( + ssid.clone(), + password.clone(), + hw_address, + ), success, state: NetworkManagerState::new(&conn).await.unwrap_or_default(), }); @@ -190,16 +196,18 @@ async fn start_listening( } else { _ = output .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::Password(ssid, password), + req: NetworkManagerRequest::Password(ssid, password, hw_address), success: false, state: NetworkManagerState::new(&conn).await.unwrap_or_default(), }) .await; } } - Some(NetworkManagerRequest::SelectAccessPoint(ssid)) => { + Some(NetworkManagerRequest::SelectAccessPoint(ssid, hw_address)) => { let state = NetworkManagerState::new(&conn).await.unwrap_or_default(); - let success = if let Err(err) = state.connect_wifi(&conn, &ssid, None).await { + let success = if let Err(err) = + state.connect_wifi(&conn, &ssid, None, hw_address).await + { tracing::error!("Failed to connect to access point: {:?}", err); false } else { @@ -208,7 +216,7 @@ async fn start_listening( _ = output .send(NetworkManagerEvent::RequestResponse { - req: NetworkManagerRequest::SelectAccessPoint(ssid.clone()), + req: NetworkManagerRequest::SelectAccessPoint(ssid.clone(), hw_address), success, state: NetworkManagerState::new(&conn).await.unwrap_or_default(), }) @@ -266,9 +274,9 @@ async fn start_listening( pub enum NetworkManagerRequest { SetAirplaneMode(bool), SetWiFi(bool), - SelectAccessPoint(String), + SelectAccessPoint(String, HwAddress), Disconnect(String), - Password(String, String), + Password(String, String, HwAddress), Forget(String), Reload, } @@ -382,7 +390,10 @@ impl NetworkManagerState { let known_access_points: Vec<_> = wireless_access_points .iter() .filter(|a| { - known_ssid.contains(&a.ssid) && !active_conns.iter().any(|ac| ac.name() == a.ssid) + known_ssid.contains(&a.ssid) + && !active_conns + .iter() + .any(|ac| ac.name() == a.ssid && ac.hw_address() == a.hw_address) }) .cloned() .collect(); @@ -407,6 +418,7 @@ impl NetworkManagerState { conn: &Connection, ssid: &str, password: Option<&str>, + hw_address: HwAddress, ) -> anyhow::Result<()> { let nm = NetworkManager::new(conn).await?; @@ -423,7 +435,7 @@ impl NetworkManagerState { let Some(ap) = self .wireless_access_points .iter() - .find(|ap| ap.ssid == ssid) + .find(|ap| ap.ssid == ssid && ap.hw_address == hw_address) else { return Err(anyhow::anyhow!("Access point not found")); }; @@ -468,13 +480,19 @@ impl NetworkManagerState { let settings = c.get_settings().await.ok().unwrap_or_default(); let s = Settings::new(settings); + let cur_hw_address = s + .wifi + .as_ref() + .and_then(|w| w.assigned_mac_address.as_ref()) + .and_then(|mac| HwAddress::from_string(&mac)); + // s.wifi.clone(). if let Some(cur_ssid) = s .wifi .clone() .and_then(|w| w.ssid) .and_then(|ssid| String::from_utf8(ssid).ok()) { - if cur_ssid == ssid { + if cur_ssid == ssid && hw_address == cur_hw_address.unwrap_or_default() { known_conn = Some(c); break; }