use crate::{ config::Config, core::{CoreManager, handle, tray}, feat::clean_async, process::AsyncHandler, utils, }; use bytes::BytesMut; use clash_verge_logging::{Type, logging}; use once_cell::sync::Lazy; use serde_yaml_ng::{Mapping, Value}; use smartstring::alias::String; use std::sync::Arc; #[allow(clippy::expect_used)] static TLS_CONFIG: Lazy> = Lazy::new(|| { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); let config = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) .with_safe_default_protocol_versions() .expect("Failed to set TLS versions") .with_root_certificates(root_store) .with_no_client_auth(); Arc::new(config) }); /// Restart the Clash core pub async fn restart_clash_core() { match CoreManager::global().restart_core().await { Ok(_) => { handle::Handle::refresh_clash(); handle::Handle::notice_message("set_config::ok", "ok"); } Err(err) => { handle::Handle::notice_message("set_config::error", format!("{err}")); logging!(error, Type::Core, "{err}"); } } } /// Restart the application pub async fn restart_app() { logging!(debug, Type::System, "启动重启应用流程"); // 设置退出标志 handle::Handle::global().set_is_exiting(); utils::server::shutdown_embedded_server(); Config::apply_all_and_save_file().await; logging!(info, Type::System, "开始异步清理资源"); let cleanup_result = clean_async().await; logging!( info, Type::System, "资源清理完成,退出代码: {}", if cleanup_result { 0 } else { 1 } ); let app_handle = handle::Handle::app_handle(); app_handle.restart(); } fn after_change_clash_mode() { AsyncHandler::spawn(move || async { let mihomo = handle::Handle::mihomo().await; match mihomo.get_connections().await { Ok(connections) => { if let Some(connections_array) = connections.connections { for connection in connections_array { let _ = mihomo.close_connection(&connection.id).await; } drop(mihomo); } } Err(err) => { logging!(error, Type::Core, "Failed to get connections: {err}"); } } }); } /// Change Clash mode (rule/global/direct/script) pub async fn change_clash_mode(mode: String) { let mut mapping = Mapping::new(); mapping.insert(Value::from("mode"), Value::from(mode.as_str())); // Convert YAML mapping to JSON Value let json_value = serde_json::json!({ "mode": mode }); logging!(debug, Type::Core, "change clash mode to {mode}"); match handle::Handle::mihomo().await.patch_base_config(&json_value).await { Ok(_) => { // 更新订阅 let clash = Config::clash().await; clash.edit_draft(|d| d.patch_config(&mapping)); clash.apply(); // 分离数据获取和异步调用 let clash_data = clash.data_arc(); if clash_data.save_config().await.is_ok() { handle::Handle::refresh_clash(); tray::Tray::global().update_menu_and_icon().await; } let is_auto_close_connection = Config::verge().await.data_arc().auto_close_connection.unwrap_or(false); if is_auto_close_connection { after_change_clash_mode(); } } Err(err) => logging!(error, Type::Core, "{err}"), } } /// Test delay to a URL through proxy. /// HTTPS: measures TLS handshake time. HTTP: measures HEAD round-trip time. pub async fn test_delay(url: String) -> anyhow::Result { use std::sync::Arc; use std::time::Duration; use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; use tokio::net::TcpStream; use tokio::time::Instant; let parsed = tauri::Url::parse(&url)?; let is_https = parsed.scheme() == "https"; let host = parsed .host_str() .ok_or_else(|| anyhow::anyhow!("Invalid URL: no host"))? .to_string(); let port = parsed.port().unwrap_or(if is_https { 443 } else { 80 }); let verge = Config::verge().await.latest_arc(); let proxy_enabled = verge.enable_system_proxy.unwrap_or(false) || verge.enable_tun_mode.unwrap_or(false); let proxy_port = if proxy_enabled { Some(match verge.verge_mixed_port { Some(p) => p, None => Config::clash().await.data_arc().get_mixed_port(), }) } else { None }; tokio::time::timeout(Duration::from_secs(10), async { let start = Instant::now(); let mut buf = BytesMut::with_capacity(1024); if is_https { let stream = match proxy_port { Some(pp) => { let mut s = TcpStream::connect(format!("127.0.0.1:{pp}")).await?; s.write_all(format!("CONNECT {host}:{port} HTTP/1.1\r\nHost: {host}:{port}\r\n\r\n").as_bytes()) .await?; s.read_buf(&mut buf).await?; if !buf.windows(3).any(|w| w == b"200") { return Err(anyhow::anyhow!("Proxy CONNECT failed")); } s } None => TcpStream::connect(format!("{host}:{port}")).await?, }; let connector = tokio_rustls::TlsConnector::from(Arc::clone(&TLS_CONFIG)); let server_name = rustls::pki_types::ServerName::try_from(host.as_str()) .map_err(|_| anyhow::anyhow!("Invalid DNS name: {host}"))? .to_owned(); connector.connect(server_name, stream).await?; } else { let (mut stream, req) = match proxy_port { Some(pp) => ( TcpStream::connect(format!("127.0.0.1:{pp}")).await?, format!("HEAD {url} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"), ), None => ( TcpStream::connect(format!("{host}:{port}")).await?, format!("HEAD / HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n"), ), }; stream.write_all(req.as_bytes()).await?; let _ = stream.read(&mut buf).await?; } // frontend treats 0 as timeout Ok((start.elapsed().as_millis() as u32).max(1)) }) .await .unwrap_or(Ok(10000u32)) }