client/common: support new login screen

This commit is contained in:
2026-05-05 05:39:45 +00:00
committed by Sam Sartor
parent e72bb6d4c4
commit e295783fe4
10 changed files with 65 additions and 30 deletions
+7 -1
View File
@@ -21,10 +21,16 @@ pub struct AudioSettings {
pub denoise: bool, pub denoise: bool,
} }
#[derive(Debug, Clone)]
pub enum ConnectTarget {
Direct { host: String, port: u16 },
Proxy(String),
}
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
Connect { Connect {
address: String, target: ConnectTarget,
username: String, username: String,
config: ProxyOverrides, config: ProxyOverrides,
}, },
+12 -6
View File
@@ -1,5 +1,5 @@
use crate::app::{Command, SharedState}; use crate::app::{Command, ConnectTarget, SharedState};
use color_eyre::eyre::Error; use color_eyre::eyre::{bail, Error};
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use mumble_protocol::control::ClientControlCodec; use mumble_protocol::control::ClientControlCodec;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
@@ -70,7 +70,7 @@ impl ServerCertVerifier for NoCertificateVerification {
#[instrument] #[instrument]
pub async fn network_connect( pub async fn network_connect(
address: String, target: ConnectTarget,
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
overrides: &ProxyOverrides, overrides: &ProxyOverrides,
@@ -78,6 +78,13 @@ pub async fn network_connect(
) -> Result<(), Error> { ) -> Result<(), Error> {
info!("connecting"); 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() let config = ClientConfig::builder()
.dangerous() .dangerous()
.with_custom_certificate_verifier(Arc::new(NoCertificateVerification)) .with_custom_certificate_verifier(Arc::new(NoCertificateVerification))
@@ -85,15 +92,14 @@ pub async fn network_connect(
let connector = TlsConnector::from(Arc::new(config)); let connector = TlsConnector::from(Arc::new(config));
let addr = format!("{}:{}", address, 64738) let addr = (&*host, port)
.to_socket_addrs()? .to_socket_addrs()?
.next() .next()
.unwrap(); .unwrap();
let server_tcp = TcpStream::connect(addr).await?; let server_tcp = TcpStream::connect(addr).await?;
let server_stream = connector let server_stream = connector
//.connect("127.0.0.1".try_into()?, server_tcp) .connect(host.try_into()?, server_tcp)
.connect(address.try_into()?, server_tcp)
.await?; .await?;
let (read_server, write_server) = tokio::io::split(server_stream); let (read_server, write_server) = tokio::io::split(server_stream);
+8 -4
View File
@@ -1,4 +1,4 @@
use crate::app::{Command, SharedState}; use crate::app::{Command, ConnectTarget, SharedState};
use color_eyre::eyre::Error; use color_eyre::eyre::Error;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use mumble_web2_common::{ProxyOverrides, ServerStatus}; use mumble_web2_common::{ProxyOverrides, ServerStatus};
@@ -24,20 +24,24 @@ impl super::PlatformInterface for DesktopPlatform {
} }
async fn network_connect( async fn network_connect(
address: String, target: ConnectTarget,
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
overrides: &ProxyOverrides, overrides: &ProxyOverrides,
state: SharedState, state: SharedState,
) -> Result<(), Error> { ) -> 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( async fn get_status(
_client: &reqwest::Client, _client: &reqwest::Client,
address: &str, address: &str,
) -> color_eyre::Result<ServerStatus> { ) -> color_eyre::Result<ServerStatus> {
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() { fn init_logging() {
+8 -4
View File
@@ -1,4 +1,4 @@
use crate::app::{Command, SharedState}; use crate::app::{Command, ConnectTarget, SharedState};
use color_eyre::eyre::Error; use color_eyre::eyre::Error;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use mumble_web2_common::{ProxyOverrides, ServerStatus}; use mumble_web2_common::{ProxyOverrides, ServerStatus};
@@ -20,20 +20,24 @@ impl super::PlatformInterface for MobilePlatform {
} }
async fn network_connect( async fn network_connect(
address: String, target: ConnectTarget,
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
overrides: &ProxyOverrides, overrides: &ProxyOverrides,
state: SharedState, state: SharedState,
) -> Result<(), Error> { ) -> 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( async fn get_status(
_client: &reqwest::Client, _client: &reqwest::Client,
address: &str, address: &str,
) -> color_eyre::Result<ServerStatus> { ) -> color_eyre::Result<ServerStatus> {
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() { fn init_logging() {
+2 -2
View File
@@ -4,7 +4,7 @@
//! The traits make the platform boundary explicit and provide compile-time verification. //! The traits make the platform boundary explicit and provide compile-time verification.
#![allow(async_fn_in_trait)] #![allow(async_fn_in_trait)]
use crate::app::{Command, SharedState}; use crate::app::{Command, ConnectTarget, SharedState};
use crate::effects::AudioProcessor; use crate::effects::AudioProcessor;
use color_eyre::eyre::Error; use color_eyre::eyre::Error;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
@@ -79,7 +79,7 @@ pub trait PlatformInterface {
/// Establish a connection to the Mumble server and run the network loop. /// Establish a connection to the Mumble server and run the network loop.
fn network_connect( fn network_connect(
address: String, target: ConnectTarget,
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
proxy_overrides: &ProxyOverrides, proxy_overrides: &ProxyOverrides,
+2 -6
View File
@@ -28,12 +28,8 @@ impl super::ConfigSystemInterface for NativeConfigSystem {
match serde_json::from_value::<T>(value_untyped) { match serde_json::from_value::<T>(value_untyped) {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(_) => { Err(_) => {
let default_value = config_get_default(key) let default_value = config_get_default(key)?;
.expect("Default value required after config parse failure"); serde_json::from_value::<T>(default_value).ok()
Some(
serde_json::from_value::<T>(default_value)
.expect("Default value could not be parsed"),
)
} }
} }
} }
+5 -2
View File
@@ -1,6 +1,9 @@
/// Stub implementation of the platform interface, so that we can /// Stub implementation of the platform interface, so that we can
/// `cargo check` without any --feature flags. /// `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 color_eyre::eyre::Error;
use futures_channel::mpsc::UnboundedReceiver; use futures_channel::mpsc::UnboundedReceiver;
use mumble_web2_common::{ProxyOverrides, ServerStatus}; use mumble_web2_common::{ProxyOverrides, ServerStatus};
@@ -21,7 +24,7 @@ impl super::PlatformInterface for StubPlatform {
} }
fn network_connect( fn network_connect(
_address: String, _target: ConnectTarget,
_username: String, _username: String,
_event_rx: &mut UnboundedReceiver<crate::app::Command>, _event_rx: &mut UnboundedReceiver<crate::app::Command>,
_overrides: &ProxyOverrides, _overrides: &ProxyOverrides,
+9 -3
View File
@@ -1,4 +1,4 @@
use crate::app::{Command, SharedState}; use crate::app::{Command, ConnectTarget, SharedState};
use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState}; use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState};
use color_eyre::eyre::{bail, eyre, Error}; use color_eyre::eyre::{bail, eyre, Error};
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
@@ -108,13 +108,19 @@ impl super::PlatformInterface for WebPlatform {
} }
async fn network_connect( async fn network_connect(
address: String, target: ConnectTarget,
username: String, username: String,
event_rx: &mut UnboundedReceiver<Command>, event_rx: &mut UnboundedReceiver<Command>,
overrides: &ProxyOverrides, overrides: &ProxyOverrides,
state: SharedState, state: SharedState,
) -> Result<(), Error> { ) -> 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( async fn get_status(
+2 -2
View File
@@ -39,7 +39,7 @@ use crate::imp::{
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>, state: SharedState) { pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>, state: SharedState) {
loop { loop {
let Some(Command::Connect { let Some(Command::Connect {
address, target,
username, username,
config, config,
}) = event_rx.next().await }) = event_rx.next().await
@@ -50,7 +50,7 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>, state:
*state.server.write_unchecked() = Default::default(); *state.server.write_unchecked() = Default::default();
*state.status.write_unchecked() = ConnectionState::Connecting; *state.status.write_unchecked() = ConnectionState::Connecting;
if let Err(error) = 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 .await
{ {
error!("could not connect {:?}", error); error!("could not connect {:?}", error);
+10
View File
@@ -17,6 +17,16 @@ pub struct ServerStatus {
pub bandwidth: Option<u32>, pub bandwidth: Option<u32>,
} }
#[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<String>,
}
/// Mumble UDP ping protocol. /// Mumble UDP ping protocol.
/// ///
/// Send a 12-byte packet: 4 zero bytes + 8-byte identifier. /// Send a 12-byte packet: 4 zero bytes + 8-byte identifier.