diff --git a/common/src/lib.rs b/common/src/lib.rs index 87c3c72..0d520a5 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize, Serialize, Default)] pub struct ClientConfig { pub proxy_url: Option, - pub status_url: Option, pub cert_hash: Option>, pub any_server: bool, } diff --git a/config.toml.example b/config.toml.example index 9bb4d0c..60b6bfb 100644 --- a/config.toml.example +++ b/config.toml.example @@ -1,4 +1,4 @@ -public_url = "https://127.0.0.1:4433" +proxy_url = "https://127.0.0.1:4433/proxy" https_listen_address = "127.0.0.1:4433" http_listen_address = "127.0.0.1:8080" mumble_server_url = "[SERVER_URL_HERE]" diff --git a/docker/proxy-config.toml b/docker/proxy-config.toml index 53eade9..72492ff 100644 --- a/docker/proxy-config.toml +++ b/docker/proxy-config.toml @@ -1,4 +1,3 @@ -public_url = "https://localhost:64444" proxy_url = "https://127.0.0.1:4433/proxy" https_listen_address = "127.0.0.1:4433" http_listen_address = "127.0.0.1:4400" diff --git a/gui/assets/main.scss b/gui/assets/main.scss index 6682bb7..0b4cc09 100644 --- a/gui/assets/main.scss +++ b/gui/assets/main.scss @@ -279,6 +279,11 @@ a:visited { color: #b3c6b4; } + &_version { + color: var(--txt-color); + font-weight: normal; + } + &_bttn { font-weight: bold; font-size: large; diff --git a/gui/build.rs b/gui/build.rs index ab4c843..8af6acb 100644 --- a/gui/build.rs +++ b/gui/build.rs @@ -1,7 +1,39 @@ +use std::env; use std::path::Path; use std::process::Command; -fn main() { +fn version_env() -> Option<()> { + if env::var("MUMBLE_WEB2_VERSION").is_ok() { + return Some(()); + } + + let output = Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output() + .ok()?; + + let git_hash = String::from_utf8(output.stdout).ok()?; + let git_hash = git_hash.trim(); // drop trailing newline + + let status = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok()?; + let dirty = match status.stdout.is_empty() { + true => "", + false => "-dirty", + }; + + // Expose it as a compile-time env var + println!("cargo::rustc-env=MUMBLE_WEB2_VERSION=git-{git_hash}{dirty}"); + + // Optional: rebuild when HEAD changes + println!("cargo::rerun-if-changed=.git/HEAD"); + + Some(()) +} + +fn download_deepfilternet() { // Define the target directory and file let assets_dir = "assets"; let target_file = format!("{}/DeepFilterNet3_ll_onnx.tar.gz", assets_dir); @@ -9,30 +41,46 @@ fn main() { // Check if the file already exists if target_path.exists() { - println!("cargo:warning=DeepFilterNet model already exists at {}", target_file); + println!( + "cargo::warning=DeepFilterNet model already exists at {}", + target_file + ); return; } - println!("cargo:warning=Downloading DeepFilterNet model to {}...", target_file); + println!( + "cargo::warning=Downloading DeepFilterNet model to {}...", + target_file + ); // Download the file using curl let url = "https://github.com/Rikorose/DeepFilterNet/raw/refs/heads/main/models/DeepFilterNet3_ll_onnx.tar.gz"; let status = Command::new("curl") .args([ - "-L", // Follow redirects - "-o", &target_file, // Output file + "-L", // Follow redirects + "-o", + &target_file, // Output file url, ]) .status() .expect("Failed to execute curl command. Make sure curl is installed."); if !status.success() { - panic!("Failed to download DeepFilterNet model from {}", url); + println!("cargo::error=Failed to download DeepFilterNet model from {url}"); + return; } - println!("cargo:warning=Successfully downloaded DeepFilterNet model to {}", target_file); + println!( + "cargo::warning=Successfully downloaded DeepFilterNet model to {}", + target_file + ); // Rerun this build script if the target file is deleted - println!("cargo:rerun-if-changed={}", target_file); + println!("cargo::rerun-if-changed={}", target_file); +} + +fn main() { + version_env(); + download_deepfilternet(); } diff --git a/gui/src/app.rs b/gui/src/app.rs index f6f2f34..efe2ff8 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -545,33 +545,15 @@ pub fn ServerView(config: Resource) -> Element { ) } -async fn get_status( - client: &reqwest::Client, - status_url: &str, -) -> color_eyre::Result { - Ok(client - .get(status_url) - .send() - .await? - .json::() - .await?) -} - #[component] pub fn LoginView(config: Resource) -> Element { let net: Coroutine = use_coroutine_handle(); let last_status = use_signal(|| None::>); use_resource(move || async move { - let Some(config) = config.read().clone() else { - return; - }; - let Some(status_url) = config.status_url else { - return; - }; let client = reqwest::Client::new(); loop { - *last_status.write_unchecked() = Some(get_status(&client, &status_url).await); + *last_status.write_unchecked() = Some(imp::get_status(&client).await); imp::sleep(std::time::Duration::from_secs_f32(1.0)).await; } }); @@ -630,11 +612,16 @@ pub fn LoginView(config: Resource) -> Element { ), Connected => unreachable!(), }; + let version = option_env!("MUMBLE_WEB2_VERSION"); rsx!( div { class: "login", h1 { "Mumble Web" + match version { + Some(v) => rsx!(" " span { class: "login_version", "({v})" }), + None => rsx!(), + } } if config.read().as_ref().is_some_and(|cfg| cfg.any_server) { div { diff --git a/gui/src/imp/desktop.rs b/gui/src/imp/desktop.rs index 8e21adc..8337718 100644 --- a/gui/src/imp/desktop.rs +++ b/gui/src/imp/desktop.rs @@ -1,11 +1,11 @@ use crate::app::Command; use crate::effects::{AudioProcessor, AudioProcessorSender}; -use color_eyre::eyre::{eyre, Context, Error}; +use color_eyre::eyre::{bail, eyre, Context, Error}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait as _}; use dioxus::hooks::UnboundedReceiver; use futures::io::{AsyncRead, AsyncWrite}; use mumble_protocol::control::ClientControlCodec; -use mumble_web2_common::ClientConfig; +use mumble_web2_common::{ClientConfig, ServerStatus}; use std::mem::replace; use std::net::ToSocketAddrs; use std::sync::Arc; @@ -64,26 +64,34 @@ impl AudioSystem { self.processors.store(Some(processor)) } - fn choose_config(&self, configs: impl Iterator) -> Result { + fn choose_config( + &self, + configs: impl Iterator, + ) -> Result { let mut supported_configs: Vec<_> = configs .filter_map(|cfg| cfg.try_with_sample_rate(cpal::SampleRate(SAMPLE_RATE))) .filter(|cfg| cfg.sample_format() == cpal::SampleFormat::I16) - .map(|cfg| { - cpal::StreamConfig { - buffer_size: cpal::BufferSize::Fixed(match *cfg.buffer_size() { - cpal::SupportedBufferSize::Range { min, max } => 480.clamp(min, max), - cpal::SupportedBufferSize::Unknown => 480, - }), - ..cfg.config() - } + .map(|cfg| cpal::StreamConfig { + buffer_size: cpal::BufferSize::Fixed(match *cfg.buffer_size() { + cpal::SupportedBufferSize::Range { min, max } => 480.clamp(min, max), + cpal::SupportedBufferSize::Unknown => 480, + }), + ..cfg.config() }) .collect(); supported_configs.sort_by(|a, b| { - let cpal::BufferSize::Fixed(a_buf) = a.buffer_size else { unreachable!() }; - let cpal::BufferSize::Fixed(b_buf) = b.buffer_size else { unreachable!() }; + let cpal::BufferSize::Fixed(a_buf) = a.buffer_size else { + unreachable!() + }; + let cpal::BufferSize::Fixed(b_buf) = b.buffer_size else { + unreachable!() + }; Ord::cmp(&a.channels, &b.channels).then(Ord::cmp(&a_buf, &b_buf)) }); - supported_configs.get(0).cloned().ok_or(eyre!("no supported stream configs")) + supported_configs + .get(0) + .cloned() + .ok_or(eyre!("no supported stream configs")) } pub fn start_recording( @@ -91,7 +99,11 @@ impl AudioSystem { mut each: impl FnMut(Vec) + Send + 'static, ) -> Result<(), Error> { let config = self.choose_config(self.input.supported_input_configs()?)?; - info!("creating recording on {:?} with {:#?}", self.input.name()?, config); + info!( + "creating recording on {:?} with {:#?}", + self.input.name()?, + config + ); let mut encoder = opus::Encoder::new(SAMPLE_RATE, opus::Channels::Mono, opus::Application::Voip)?; let mut current_processor = AudioProcessor::new_plain(); @@ -118,12 +130,10 @@ impl AudioSystem { } }; - match self.input.build_input_stream( - &config, - data_callback, - error_callback, - None, - ) { + match self + .input + .build_input_stream(&config, data_callback, error_callback, None) + { Ok(stream) => { stream.play()?; self.recording_stream = Some(stream); @@ -138,7 +148,11 @@ impl AudioSystem { pub fn create_player(&mut self) -> Result { let config = self.choose_config(self.input.supported_input_configs()?)?; - info!("creating player on {:?} with {:#?}", self.output.name().ok(), &config); + info!( + "creating player on {:?} with {:#?}", + self.output.name().ok(), + &config + ); let buffer = Arc::new(Mutex::new(dasp_ring_buffer::Bounded::from_raw_parts( 0, 0, @@ -311,12 +325,15 @@ pub fn load_username() -> Option { pub async fn load_config() -> color_eyre::Result { Ok(ClientConfig { proxy_url: None, - status_url: None, cert_hash: None, any_server: true, }) } +pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result { + bail!("status not supported on desktop yet") +} + pub fn init_logging() { use tracing::level_filters::LevelFilter; use tracing_subscriber::filter::EnvFilter; diff --git a/gui/src/imp/web.rs b/gui/src/imp/web.rs index 9272158..297c323 100644 --- a/gui/src/imp/web.rs +++ b/gui/src/imp/web.rs @@ -6,7 +6,7 @@ use futures::{AsyncRead, AsyncWrite}; use gloo_timers::future::TimeoutFuture; use js_sys::Float32Array; use mumble_protocol::control::ClientControlCodec; -use mumble_web2_common::ClientConfig; +use mumble_web2_common::{ClientConfig, ServerStatus}; use reqwest::Url; use std::future::Future; use std::time::Duration; @@ -426,6 +426,15 @@ pub async fn load_config() -> color_eyre::Result { Ok(config) } +pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result { + Ok(client + .get(absolute_url("status")?) + .send() + .await? + .json::() + .await?) +} + pub fn init_logging() { // copied from tracing_web example usage diff --git a/proxy/src/main.rs b/proxy/src/main.rs index 35031cf..90def5e 100644 --- a/proxy/src/main.rs +++ b/proxy/src/main.rs @@ -1,10 +1,6 @@ use color_eyre::eyre::{anyhow, bail, Context, Result}; -use color_eyre::owo_colors::OwoColorize; use mumble_web2_common::{ClientConfig, ServerStatus}; -use once_cell::sync::OnceCell; use rand::Rng; -use rcgen::date_time_ymd; -use rustls::server; use salvo::conn::rustls::{Keycert, RustlsConfig}; use salvo::cors::{AllowOrigin, Cors}; use salvo::logging::Logger; @@ -38,7 +34,6 @@ fn default_cert_alt_names() -> Vec { #[derive(Debug, Deserialize, Serialize)] struct Config { - public_url: Url, proxy_url: Option, https_listen_address: SocketAddr, http_listen_address: Option, @@ -85,9 +80,8 @@ async fn main() -> Result<()> { let mut client_config = ClientConfig { proxy_url: match &server_config.proxy_url { Some(url) => Some(url.to_string()), - None => Some(server_config.public_url.join("proxy")?.to_string()), + None => None, }, - status_url: Some(server_config.public_url.join("status")?.to_string()), cert_hash: None, any_server: false, };