From e295783fe433ee651c5958a6c98274f768b77b37 Mon Sep 17 00:00:00 2001 From: restitux Date: Tue, 5 May 2026 05:39:45 +0000 Subject: [PATCH] client/common: support new login screen --- client/src/app.rs | 8 +++++++- client/src/imp/connect.rs | 18 ++++++++++++------ client/src/imp/desktop.rs | 12 ++++++++---- client/src/imp/mobile.rs | 12 ++++++++---- client/src/imp/mod.rs | 4 ++-- client/src/imp/native_config.rs | 8 ++------ client/src/imp/stub.rs | 7 +++++-- client/src/imp/web.rs | 12 +++++++++--- client/src/mainloop.rs | 4 ++-- common/src/lib.rs | 10 ++++++++++ 10 files changed, 65 insertions(+), 30 deletions(-) diff --git a/client/src/app.rs b/client/src/app.rs index 41f8721..67c128a 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -21,10 +21,16 @@ pub struct AudioSettings { pub denoise: bool, } +#[derive(Debug, Clone)] +pub enum ConnectTarget { + Direct { host: String, port: u16 }, + Proxy(String), +} + #[derive(Debug)] pub enum Command { Connect { - address: String, + target: ConnectTarget, username: String, config: ProxyOverrides, }, diff --git a/client/src/imp/connect.rs b/client/src/imp/connect.rs index b6654b4..155101d 100644 --- a/client/src/imp/connect.rs +++ b/client/src/imp/connect.rs @@ -1,5 +1,5 @@ -use crate::app::{Command, SharedState}; -use color_eyre::eyre::Error; +use crate::app::{Command, ConnectTarget, SharedState}; +use color_eyre::eyre::{bail, Error}; use futures_channel::mpsc::UnboundedReceiver; use mumble_protocol::control::ClientControlCodec; use std::net::ToSocketAddrs; @@ -70,7 +70,7 @@ impl ServerCertVerifier for NoCertificateVerification { #[instrument] pub async fn network_connect( - address: String, + target: ConnectTarget, username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, @@ -78,6 +78,13 @@ pub async fn network_connect( ) -> Result<(), Error> { info!("connecting"); + let (host, port) = match target { + ConnectTarget::Direct { host, port } => (host, port), + ConnectTarget::Proxy(_) => { + bail!("desktop/mobile platform requires a direct host:port, not a proxy URL") + } + }; + let config = ClientConfig::builder() .dangerous() .with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) @@ -85,15 +92,14 @@ pub async fn network_connect( let connector = TlsConnector::from(Arc::new(config)); - let addr = format!("{}:{}", address, 64738) + let addr = (&*host, port) .to_socket_addrs()? .next() .unwrap(); let server_tcp = TcpStream::connect(addr).await?; let server_stream = connector - //.connect("127.0.0.1".try_into()?, server_tcp) - .connect(address.try_into()?, server_tcp) + .connect(host.try_into()?, server_tcp) .await?; let (read_server, write_server) = tokio::io::split(server_stream); diff --git a/client/src/imp/desktop.rs b/client/src/imp/desktop.rs index 9da9e62..9df8f49 100644 --- a/client/src/imp/desktop.rs +++ b/client/src/imp/desktop.rs @@ -1,4 +1,4 @@ -use crate::app::{Command, SharedState}; +use crate::app::{Command, ConnectTarget, SharedState}; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -24,20 +24,24 @@ impl super::PlatformInterface for DesktopPlatform { } async fn network_connect( - address: String, + target: ConnectTarget, username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, state: SharedState, ) -> Result<(), Error> { - super::connect::network_connect(address, username, event_rx, overrides, state).await + super::connect::network_connect(target, username, event_rx, overrides, state).await } async fn get_status( _client: &reqwest::Client, address: &str, ) -> color_eyre::Result { - mumble_web2_common::ping_server(address, 64738).await + let (host, port) = match address.rsplit_once(':') { + Some((h, p)) => (h, p.parse().unwrap_or(64738)), + None => (address, 64738), + }; + mumble_web2_common::ping_server(host, port).await } fn init_logging() { diff --git a/client/src/imp/mobile.rs b/client/src/imp/mobile.rs index a973920..9fb4906 100644 --- a/client/src/imp/mobile.rs +++ b/client/src/imp/mobile.rs @@ -1,4 +1,4 @@ -use crate::app::{Command, SharedState}; +use crate::app::{Command, ConnectTarget, SharedState}; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -20,20 +20,24 @@ impl super::PlatformInterface for MobilePlatform { } async fn network_connect( - address: String, + target: ConnectTarget, username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, state: SharedState, ) -> Result<(), Error> { - super::connect::network_connect(address, username, event_rx, overrides, state).await + super::connect::network_connect(target, username, event_rx, overrides, state).await } async fn get_status( _client: &reqwest::Client, address: &str, ) -> color_eyre::Result { - mumble_web2_common::ping_server(address, 64738).await + let (host, port) = match address.rsplit_once(':') { + Some((h, p)) => (h, p.parse().unwrap_or(64738)), + None => (address, 64738), + }; + mumble_web2_common::ping_server(host, port).await } fn init_logging() { diff --git a/client/src/imp/mod.rs b/client/src/imp/mod.rs index c9bd707..4bdb032 100644 --- a/client/src/imp/mod.rs +++ b/client/src/imp/mod.rs @@ -4,7 +4,7 @@ //! The traits make the platform boundary explicit and provide compile-time verification. #![allow(async_fn_in_trait)] -use crate::app::{Command, SharedState}; +use crate::app::{Command, ConnectTarget, SharedState}; use crate::effects::AudioProcessor; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; @@ -79,7 +79,7 @@ pub trait PlatformInterface { /// Establish a connection to the Mumble server and run the network loop. fn network_connect( - address: String, + target: ConnectTarget, username: String, event_rx: &mut UnboundedReceiver, proxy_overrides: &ProxyOverrides, diff --git a/client/src/imp/native_config.rs b/client/src/imp/native_config.rs index 6ae3b20..1a0c3c7 100644 --- a/client/src/imp/native_config.rs +++ b/client/src/imp/native_config.rs @@ -28,12 +28,8 @@ impl super::ConfigSystemInterface for NativeConfigSystem { match serde_json::from_value::(value_untyped) { Ok(v) => Some(v), Err(_) => { - let default_value = config_get_default(key) - .expect("Default value required after config parse failure"); - Some( - serde_json::from_value::(default_value) - .expect("Default value could not be parsed"), - ) + let default_value = config_get_default(key)?; + serde_json::from_value::(default_value).ok() } } } diff --git a/client/src/imp/stub.rs b/client/src/imp/stub.rs index 6744514..5f4c42d 100644 --- a/client/src/imp/stub.rs +++ b/client/src/imp/stub.rs @@ -1,6 +1,9 @@ /// Stub implementation of the platform interface, so that we can /// `cargo check` without any --feature flags. -use crate::{app::SharedState, effects::AudioProcessor}; +use crate::{ + app::{ConnectTarget, SharedState}, + effects::AudioProcessor, +}; use color_eyre::eyre::Error; use futures_channel::mpsc::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; @@ -21,7 +24,7 @@ impl super::PlatformInterface for StubPlatform { } fn network_connect( - _address: String, + _target: ConnectTarget, _username: String, _event_rx: &mut UnboundedReceiver, _overrides: &ProxyOverrides, diff --git a/client/src/imp/web.rs b/client/src/imp/web.rs index 83080ec..b23219a 100644 --- a/client/src/imp/web.rs +++ b/client/src/imp/web.rs @@ -1,4 +1,4 @@ -use crate::app::{Command, SharedState}; +use crate::app::{Command, ConnectTarget, SharedState}; use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState}; use color_eyre::eyre::{bail, eyre, Error}; use crossbeam::atomic::AtomicCell; @@ -108,13 +108,19 @@ impl super::PlatformInterface for WebPlatform { } async fn network_connect( - address: String, + target: ConnectTarget, username: String, event_rx: &mut UnboundedReceiver, overrides: &ProxyOverrides, state: SharedState, ) -> Result<(), Error> { - network_connect(address, username, event_rx, overrides, state).await + let url = match target { + ConnectTarget::Proxy(url) => url, + ConnectTarget::Direct { .. } => { + bail!("web platform requires a proxy URL, not a direct host:port") + } + }; + network_connect(url, username, event_rx, overrides, state).await } async fn get_status( diff --git a/client/src/mainloop.rs b/client/src/mainloop.rs index b6451e5..0a369c4 100644 --- a/client/src/mainloop.rs +++ b/client/src/mainloop.rs @@ -39,7 +39,7 @@ use crate::imp::{ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver, state: SharedState) { loop { let Some(Command::Connect { - address, + target, username, config, }) = event_rx.next().await @@ -50,7 +50,7 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver, state: *state.server.write_unchecked() = Default::default(); *state.status.write_unchecked() = ConnectionState::Connecting; if let Err(error) = - Platform::network_connect(address, username, &mut event_rx, &config, state.clone()) + Platform::network_connect(target, username, &mut event_rx, &config, state.clone()) .await { error!("could not connect {:?}", error); diff --git a/common/src/lib.rs b/common/src/lib.rs index a3277a6..1c66ce2 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -17,6 +17,16 @@ pub struct ServerStatus { pub bandwidth: Option, } +#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)] +pub struct ServerEntry { + pub name: String, + pub address: String, + pub port: u16, + pub username: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub password: Option, +} + /// Mumble UDP ping protocol. /// /// Send a 12-byte packet: 4 zero bytes + 8-byte identifier.