From 8587151b1ac91a811fa8f19247a003d50b36fb90 Mon Sep 17 00:00:00 2001 From: Hassan Q Date: Sun, 24 May 2026 23:20:28 +0330 Subject: [PATCH] feat: add enable system proxy button for windows --- Cargo.lock | 13 ++++- Cargo.toml | 1 + src/bin/ui.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 141 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e17c71f5..449fdf8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2655,6 +2655,7 @@ dependencies = [ "tun2proxy", "url", "webpki-roots 0.26.11", + "winreg 0.56.0", "x509-parser", "zstd", ] @@ -5694,6 +5695,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winreg" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "wintun-bindings" version = "0.7.34" @@ -5707,7 +5718,7 @@ dependencies = [ "log", "thiserror 2.0.18", "windows-sys 0.61.2", - "winreg", + "winreg 0.55.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 46070609..f8822805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ eframe = { version = "0.28", default-features = false, features = [ "accesskit", ], optional = true } url = "2.5.8" +winreg = "0.56.0" # Unix-only deps. Must come after `[dependencies]` because starting a new # table here otherwise ends the main one — anything below it (incl. eframe) diff --git a/src/bin/ui.rs b/src/bin/ui.rs index c5f9ed63..b8c155a7 100644 --- a/src/bin/ui.rs +++ b/src/bin/ui.rs @@ -30,8 +30,14 @@ fn main() -> eframe::Result<()> { // run before any data_dir / firefox_profile_dirs call. reconcile_sudo_environment(); mhrv_rs::rlimit::raise_nofile_limit_best_effort(); - let shared = Arc::new(Shared::default()); + + let system_proxy = check_system_proxy().unwrap_or_else(|_| { + push_log(&shared, "failed to read system proxy..."); + false + }); + + shared.state.lock().unwrap().system_proxy = system_proxy; let (cmd_tx, cmd_rx) = std::sync::mpsc::channel::(); // Load the user's saved form first so we can seed the tracing filter @@ -154,6 +160,7 @@ struct UiState { /// One-line status of the most recent download (Ok(path) or Err(msg)). last_download: Option>, last_download_at: Option, + system_proxy: bool, } #[derive(Clone, Debug)] @@ -205,6 +212,8 @@ enum Cmd { url: String, name: String, }, + EnableSystemProxy(Config), + DisableSystemProxy, } struct App { @@ -1156,7 +1165,7 @@ impl eframe::App for App { ui.add_space(8.0); // ── Status + stats card ──────────────────────────────────────── - let (running, started_at, stats, ca_trusted, last_test_msg, per_site) = { + let (running, started_at, stats, ca_trusted, last_test_msg, per_site, system_proxy) = { let s = self.shared.state.lock().unwrap(); ( s.running, @@ -1165,6 +1174,7 @@ impl eframe::App for App { s.ca_trusted, s.last_test_msg.clone(), s.last_per_site.clone(), + s.system_proxy, ) }; @@ -1400,6 +1410,36 @@ impl eframe::App for App { } } } + + #[cfg(target_os = "windows")] + if !system_proxy { + let btn = egui::Button::new( + egui::RichText::new("Enable System Proxy").color(egui::Color32::WHITE).strong(), + ) + .fill(OK_GREEN) + .min_size(egui::vec2(80.0, 32.0)) + .rounding(4.0); + if ui.add(btn).clicked() { + match self.form.to_config() { + Ok(cfg) => { + let _ = self.cmd_tx.send(Cmd::EnableSystemProxy(cfg)); + } + Err(e) => { + self.toast = Some((format!("Cannot start: {}", e), Instant::now())); + } + } + } + } else { + let btn = egui::Button::new( + egui::RichText::new("Disable System Proxy").color(egui::Color32::WHITE).strong(), + ) + .fill(ERR_RED) + .min_size(egui::vec2(80.0, 32.0)) + .rounding(4.0); + if ui.add(btn).clicked() { + let _ = self.cmd_tx.send(Cmd::DisableSystemProxy); + } + } }); // Secondary actions — smaller, grouped together on their own line. @@ -1899,8 +1939,7 @@ impl App { let custom_label = ui.add_sized( [0.0, 0.0], egui::Label::new( - egui::RichText::new("Custom SNI") - .color(egui::Color32::TRANSPARENT), + egui::RichText::new("Custom SNI").color(egui::Color32::TRANSPARENT), ), ); ui.add( @@ -2113,14 +2152,14 @@ fn background_thread(shared: Arc, rx: Receiver) { https://whatismyipaddress.com in your browser \ via 127.0.0.1:8085. The IP shown should be your \ tunnel-node's VPS IP. Tracking a real Full-mode \ - test in #160." + test in #160.", ), Some(mhrv_rs::config::Mode::Direct) => Some( "Test Relay is wired only for apps_script mode. \ In direct mode there is no Apps Script relay — \ every request goes through the SNI-rewrite tunnel \ straight to Google's edge. Verify by loading \ - https://www.google.com via the proxy." + https://www.google.com via the proxy.", ), _ => None, }; @@ -2375,6 +2414,76 @@ fn background_thread(shared: Arc, rx: Receiver) { } }); } + + Ok(Cmd::EnableSystemProxy(cfg)) => { + // if active.is_some() { + // push_log(&shared, "[ui] already running"); + // continue; + // } + push_log(&shared, "[ui] setting system proxy..."); + // Flip proxy_active synchronously so a `Remove CA` click + // queued in the same frame as Start is rejected before + // the MITM manager begins loading. + shared.state.lock().unwrap().system_proxy = true; + + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + let internet_settings = hkcu.open_subkey_with_flags( + "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + winreg::enums::KEY_SET_VALUE, + ); + match internet_settings { + Ok(key) => { + if let Err(e) = key.set_value("ProxyEnable", &1u32) { + push_log(&shared, &format!("[ui] failed to set ProxyEnable: {}", e)); + } + let proxy_server = format!("{}:{}", cfg.listen_host, cfg.listen_port); + if let Err(e) = key.set_value("ProxyServer", &proxy_server) { + push_log(&shared, &format!("[ui] failed to set ProxyServer: {}", e)); + } + + push_log( + &shared, + &format!( + "[ui] system proxy set to {}:{}", + cfg.listen_host, cfg.listen_port + ), + ); + } + Err(e) => { + push_log( + &shared, + &format!("[ui] failed to open internet settings: {}", e), + ); + } + } + } + + Ok(Cmd::DisableSystemProxy) => { + // push_log(&shared, "[ui] disabling system proxy..."); + shared.state.lock().unwrap().system_proxy = false; + + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + let internet_settings = hkcu.open_subkey_with_flags( + "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + winreg::enums::KEY_SET_VALUE, + ); + match internet_settings { + Ok(key) => { + if let Err(e) = key.set_value("ProxyEnable", &0u32) { + push_log(&shared, &format!("[ui] failed to set ProxyEnable: {}", e)); + } + + push_log(&shared, "[ui] system proxy disabled"); + } + Err(e) => { + push_log( + &shared, + &format!("[ui] failed to open internet settings: {}", e), + ); + } + } + } + Err(_) => {} } @@ -2492,10 +2601,7 @@ fn install_ui_tracing(shared: Arc, config_level: &str) { /// by `install_ui_tracing`. `apply_log_level` uses it to swap in a new /// filter when the user clicks Save with a different log level (#401). static LOG_RELOAD: std::sync::OnceLock< - tracing_subscriber::reload::Handle< - tracing_subscriber::EnvFilter, - tracing_subscriber::Registry, - >, + tracing_subscriber::reload::Handle, > = std::sync::OnceLock::new(); /// Reinstall the tracing filter at runtime. Called from the Save handler @@ -2568,3 +2674,15 @@ fn push_log(shared: &Shared, msg: &str) { s.log.pop_front(); } } + +#[cfg(target_os = "windows")] +fn check_system_proxy() -> std::io::Result { + let hkcu = winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER); + let internet_settings = hkcu.open_subkey_with_flags( + "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + winreg::enums::KEY_QUERY_VALUE, + )?; + let enabled: u32 = internet_settings.get_value("ProxyEnable")?; + + Ok(enabled != 0) +}