From 5067f84d92e68b187b39a47d1b4b4382a0d8c618 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Fri, 3 May 2024 12:45:08 +0200 Subject: [PATCH] implement dns indicator --- resources/client_row.ui | 8 +++- src/client.rs | 2 + src/frontend.rs | 2 + src/frontend/gtk/client_object.rs | 11 +++++ src/frontend/gtk/client_object/imp.rs | 2 + src/frontend/gtk/client_row.rs | 23 ++++++++++ src/frontend/gtk/client_row/imp.rs | 7 ++- src/frontend/gtk/window.rs | 65 ++++++++++++++++++++++++--- src/server.rs | 5 ++- src/server/frontend_task.rs | 10 +++++ src/server/resolver_task.rs | 36 ++++++++++++++- 11 files changed, 158 insertions(+), 13 deletions(-) diff --git a/resources/client_row.ui b/resources/client_row.ui index 2bdd726d..317006d3 100644 --- a/resources/client_row.ui +++ b/resources/client_row.ui @@ -13,12 +13,16 @@ - + network-wired-symbolic center end - resolve dns + resolve host + + + + diff --git a/src/client.rs b/src/client.rs index 43718671..95b470c7 100644 --- a/src/client.rs +++ b/src/client.rs @@ -139,6 +139,8 @@ pub struct ClientState { pub ips: HashSet, /// keys currently pressed by this client pub pressed_keys: HashSet, + /// dns resolving in progress + pub resolving: bool, } pub struct ClientManager { diff --git a/src/frontend.rs b/src/frontend.rs index 5777cc78..ac4b15dd 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -95,6 +95,8 @@ pub enum FrontendRequest { Delete(ClientHandle), /// request an enumeration of all clients Enumerate(), + /// resolve dns + ResolveDns(ClientHandle), /// service shutdown Terminate(), /// update hostname diff --git a/src/frontend/gtk/client_object.rs b/src/frontend/gtk/client_object.rs index 7b48f3cf..a9da3b13 100644 --- a/src/frontend/gtk/client_object.rs +++ b/src/frontend/gtk/client_object.rs @@ -17,6 +17,15 @@ impl ClientObject { .property("port", client.port as u32) .property("position", client.pos.to_string()) .property("active", state.active) + .property( + "ips", + state + .ips + .iter() + .map(|ip| ip.to_string()) + .collect::>(), + ) + .property("resolving", state.resolving) .build() } @@ -32,4 +41,6 @@ pub struct ClientData { pub port: u32, pub active: bool, pub position: String, + pub resolving: bool, + pub ips: Vec, } diff --git a/src/frontend/gtk/client_object/imp.rs b/src/frontend/gtk/client_object/imp.rs index 8ca456e8..03e9e959 100644 --- a/src/frontend/gtk/client_object/imp.rs +++ b/src/frontend/gtk/client_object/imp.rs @@ -17,6 +17,8 @@ pub struct ClientObject { #[property(name = "port", get, set, type = u32, member = port, maximum = u16::MAX as u32)] #[property(name = "active", get, set, type = bool, member = active)] #[property(name = "position", get, set, type = String, member = position)] + #[property(name = "resolving", get, set, type = bool, member = resolving)] + #[property(name = "ips", get, set, type = Vec, member = ips)] pub data: RefCell, } diff --git a/src/frontend/gtk/client_row.rs b/src/frontend/gtk/client_row.rs index 082d1853..58e11cf3 100644 --- a/src/frontend/gtk/client_row.rs +++ b/src/frontend/gtk/client_row.rs @@ -109,6 +109,27 @@ impl ClientRow { .sync_create() .build(); + let resolve_binding = client_object + .bind_property( + "resolving", + &self.imp().dns_loading_indicator.get(), + "spinning", + ) + .sync_create() + .build(); + + let ip_binding = client_object + .bind_property("ips", &self.imp().dns_button.get(), "tooltip-text") + .transform_to(|_, ips: Vec| { + if ips.is_empty() { + Some("no ip addresses associated with this client".into()) + } else { + Some(ips.join("\n")) + } + }) + .sync_create() + .build(); + bindings.push(active_binding); bindings.push(switch_position_binding); bindings.push(hostname_binding); @@ -116,6 +137,8 @@ impl ClientRow { bindings.push(port_binding); bindings.push(subtitle_binding); bindings.push(position_binding); + bindings.push(resolve_binding); + bindings.push(ip_binding); } pub fn unbind(&self) { diff --git a/src/frontend/gtk/client_row/imp.rs b/src/frontend/gtk/client_row/imp.rs index d6dead26..141173bf 100644 --- a/src/frontend/gtk/client_row/imp.rs +++ b/src/frontend/gtk/client_row/imp.rs @@ -25,6 +25,8 @@ pub struct ClientRow { pub delete_row: TemplateChild, #[template_child] pub delete_button: TemplateChild, + #[template_child] + pub dns_loading_indicator: TemplateChild, pub bindings: RefCell>, } @@ -60,6 +62,7 @@ impl ObjectImpl for ClientRow { static SIGNALS: OnceLock> = OnceLock::new(); SIGNALS.get_or_init(|| { vec![ + Signal::builder("request-dns").build(), Signal::builder("request-update") .param_types([bool::static_type()]) .build(), @@ -79,8 +82,8 @@ impl ClientRow { } #[template_callback] - fn handle_request_dns(&self) -> bool { - false + fn handle_request_dns(&self, _: Button) { + self.obj().emit_by_name::<()>("request-dns", &[]); } #[template_callback] diff --git a/src/frontend/gtk/window.rs b/src/frontend/gtk/window.rs index 7d5b8a8d..eeecb9f8 100644 --- a/src/frontend/gtk/window.rs +++ b/src/frontend/gtk/window.rs @@ -14,7 +14,7 @@ use glib::{clone, Object}; use gtk::{ gio, glib::{self, closure_local}, - NoSelection, + ListBox, NoSelection, }; use crate::{ @@ -67,12 +67,23 @@ impl Window { return; }; let client = client.downcast_ref::().unwrap(); - window.request_client_update(client, active); + window.request_client_update(client); + window.request_client_activate(client, active) })); row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| { let index = row.index() as u32; window.request_client_delete(index); })); + row.connect_closure("request-dns", false, closure_local!(@strong window => move + |row: ClientRow| { + let index = row.index() as u32; + let Some(client) = window.clients().item(index) else { + return; + }; + let client = client.downcast_ref::().unwrap(); + window.request_client_update(client); + window.request_dns(index); + })); row.upcast() }) ); @@ -100,9 +111,10 @@ impl Window { } pub fn new_client(&self, handle: ClientHandle, client: ClientConfig, state: ClientState) { - let client = ClientObject::new(handle, client, state); + let client = ClientObject::new(handle, client, state.clone()); self.clients().append(&client); self.set_placeholder_visible(false); + self.update_dns_state(handle, !state.ips.is_empty()); } pub fn client_idx(&self, handle: ClientHandle) -> Option { @@ -162,6 +174,42 @@ impl Window { client_object.set_active(state.active); log::debug!("set active to {}", state.active); } + + if state.resolving != data.resolving { + client_object.set_resolving(state.resolving); + log::debug!("resolving {}: {}", data.handle, state.active); + } + + self.update_dns_state(handle, !state.ips.is_empty()); + let ips = state + .ips + .into_iter() + .map(|ip| ip.to_string()) + .collect::>(); + client_object.set_ips(ips); + } + + pub fn update_dns_state(&self, handle: ClientHandle, resolved: bool) { + let Some(idx) = self.client_idx(handle) else { + log::warn!("could not find client with handle {}", handle); + return; + }; + let list_box: ListBox = self.imp().client_list.get(); + let row = list_box.row_at_index(idx as i32).unwrap(); + let client_row: ClientRow = row.downcast().expect("expected ClientRow Object"); + if resolved { + client_row.imp().dns_button.set_css_classes(&["success"]) + } else { + client_row.imp().dns_button.set_css_classes(&["warning"]) + } + } + + pub fn request_dns(&self, idx: u32) { + let client_object = self.clients().item(idx).unwrap(); + let client_object: &ClientObject = client_object.downcast_ref().unwrap(); + let data = client_object.get_data(); + let event = FrontendRequest::ResolveDns(data.handle); + self.request(event); } pub fn request_client_create(&self) { @@ -179,7 +227,7 @@ impl Window { } } - pub fn request_client_update(&self, client: &ClientObject, active: bool) { + pub fn request_client_update(&self, client: &ClientObject) { let handle = client.handle(); let data = client.get_data(); let position = Position::try_from(data.position.as_str()).expect("invalid position"); @@ -190,13 +238,20 @@ impl Window { FrontendRequest::UpdateHostname(handle, hostname), FrontendRequest::UpdatePosition(handle, position), FrontendRequest::UpdatePort(handle, port), - FrontendRequest::Activate(handle, active), ] { log::debug!("requesting: {event:?}"); self.request(event); } } + pub fn request_client_activate(&self, client: &ClientObject, active: bool) { + let handle = client.handle(); + + let event = FrontendRequest::Activate(handle, active); + log::debug!("requesting: {event:?}"); + self.request(event); + } + pub fn request_client_delete(&self, idx: u32) { if let Some(obj) = self.clients().item(idx) { let client_object: &ClientObject = obj diff --git a/src/server.rs b/src/server.rs index 8eb14507..b63db3f0 100644 --- a/src/server.rs +++ b/src/server.rs @@ -92,7 +92,7 @@ impl Server { // udp task let (mut udp_task, sender_tx, receiver_rx, port_tx) = - network_task::new(self.clone(), frontend_notify_tx).await?; + network_task::new(self.clone(), frontend_notify_tx.clone()).await?; // input capture let (mut capture_task, capture_channel) = capture_task::new( @@ -113,7 +113,8 @@ impl Server { // create dns resolver let resolver = dns::DnsResolver::new().await?; - let (mut resolver_task, resolve_tx) = resolver_task::new(resolver, self.clone()); + let (mut resolver_task, resolve_tx) = + resolver_task::new(resolver, self.clone(), frontend_notify_tx); // frontend listener let (mut frontend_task, frontend_tx) = frontend_task::new( diff --git a/src/server/frontend_task.rs b/src/server/frontend_task.rs index 58a657c9..abc75b10 100644 --- a/src/server/frontend_task.rs +++ b/src/server/frontend_task.rs @@ -151,6 +151,16 @@ async fn handle_frontend_event( update_pos(server, handle, capture, emulate, pos).await; broadcast_client_update(server, frontend, handle).await; } + FrontendRequest::ResolveDns(handle) => { + let hostname = server + .client_manager + .borrow() + .get(handle) + .and_then(|(c, _)| c.hostname.clone()); + if let Some(hostname) = hostname { + let _ = resolve_tx.send(DnsRequest { hostname, handle }).await; + } + } }; false } diff --git a/src/server/resolver_task.rs b/src/server/resolver_task.rs index 03c026ba..ddfb17cf 100644 --- a/src/server/resolver_task.rs +++ b/src/server/resolver_task.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use tokio::{sync::mpsc::Sender, task::JoinHandle}; -use crate::{client::ClientHandle, dns::DnsResolver}; +use crate::{client::ClientHandle, dns::DnsResolver, frontend::FrontendEvent}; use super::Server; @@ -12,7 +12,11 @@ pub struct DnsRequest { pub handle: ClientHandle, } -pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender) { +pub fn new( + resolver: DnsResolver, + mut server: Server, + mut frontend: Sender, +) -> (JoinHandle<()>, Sender) { let (dns_tx, mut dns_rx) = tokio::sync::mpsc::channel::(32); let resolver_task = tokio::task::spawn_local(async move { loop { @@ -20,6 +24,13 @@ pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender (r.hostname, r.handle), None => break, }; + + /* update resolving status */ + if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) { + s.resolving = true; + } + notify_state_change(&mut frontend, &mut server, handle).await; + let ips = match resolver.resolve(&host).await { Ok(ips) => ips, Err(e) => { @@ -27,14 +38,35 @@ pub fn new(resolver: DnsResolver, server: Server) -> (JoinHandle<()>, Sender, + server: &mut Server, + handle: ClientHandle, +) { + let state = server + .client_manager + .borrow_mut() + .get_mut(handle) + .map(|(_, s)| s.clone()); + if let Some(state) = state { + let _ = frontend + .send(FrontendEvent::StateChange(handle, state)) + .await; + } +}