diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 9360512ce..9123af8cb 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -36,10 +36,21 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult { } /// 修改Clash模式 +/// +/// 将 `change_clash_mode` 的失败上抛给前端,使前端 `catch` 能真正感知后端 PATCH 失败 +/// 并提示用户(此前命令始终返回 `Ok(())`,吞掉了后端错误)。 #[tauri::command] pub async fn patch_clash_mode(payload: String) -> CmdResult { - feat::change_clash_mode(payload).await; - Ok(()) + feat::change_clash_mode(payload).await +} + +/// 获取当前 Clash 模式(容错读取) +/// +/// 直接读取已保存的 clash 配置中的 `mode`,绕开 mihomo `/configs` 的严格 +/// `BaseConfig` 反序列化,作为主页 mode 显示的兜底来源。 +#[tauri::command] +pub async fn get_clash_mode() -> CmdResult> { + Ok(Config::clash().await.data_arc().get_mode().map(Into::into)) } /// 切换Clash核心 diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs index ea9b33e55..63f954292 100644 --- a/src-tauri/src/config/clash.rs +++ b/src-tauri/src/config/clash.rs @@ -180,6 +180,17 @@ impl IClashTemp { }), } } + + /// 容错读取当前代理模式。 + /// + /// 仅从已保存的 clash 配置 Mapping 中提取 `mode` 字段,不依赖 mihomo `/configs` + /// 的严格 `BaseConfig` 反序列化,因此即使核心返回的字段与插件结构体不匹配也能取到。 + pub fn get_mode(&self) -> Option { + self.0.get("mode").and_then(|value| match value { + Value::String(val_str) => Some(val_str.clone()), + _ => None, + }) + } #[cfg(not(target_os = "windows"))] pub fn guard_redir_port(config: &Mapping) -> u16 { let mut port = config diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index ed2aece9b..6f4cd08e7 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -125,19 +125,22 @@ impl Hotkey { } HotkeyFunction::ClashModeRule => { AsyncHandler::spawn(async move || { - feat::change_clash_mode("rule".into()).await; + // 错误已在 change_clash_mode 内部记录,此处显式忽略返回值 + let _ = feat::change_clash_mode("rule".into()).await; notify_event(NotificationEvent::ClashModeChanged { mode: "Rule" }).await; }); } HotkeyFunction::ClashModeGlobal => { AsyncHandler::spawn(async move || { - feat::change_clash_mode("global".into()).await; + // 错误已在 change_clash_mode 内部记录,此处显式忽略返回值 + let _ = feat::change_clash_mode("global".into()).await; notify_event(NotificationEvent::ClashModeChanged { mode: "Global" }).await; }); } HotkeyFunction::ClashModeDirect => { AsyncHandler::spawn(async move || { - feat::change_clash_mode("direct".into()).await; + // 错误已在 change_clash_mode 内部记录,此处显式忽略返回值 + let _ = feat::change_clash_mode("direct".into()).await; notify_event(NotificationEvent::ClashModeChanged { mode: "Direct" }).await; }); } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 8dbdc0603..ac5811138 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -933,7 +933,8 @@ fn on_menu_event(_: &AppHandle, event: MenuEvent) { && let Some(final_mode) = stripped.strip_suffix("_mode") { logging!(info, Type::ProxyMode, "Switch Proxy Mode To: {}", final_mode); - feat::change_clash_mode(final_mode.into()).await; + // 错误已在 change_clash_mode 内部记录,此处显式忽略返回值 + let _ = feat::change_clash_mode(final_mode.into()).await; } } MenuIds::DASHBOARD => { diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index 231c97bf4..c9672e25c 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -80,7 +80,10 @@ fn after_change_clash_mode() { } /// Change Clash mode (rule/global/direct/script) -pub async fn change_clash_mode(mode: String) { +/// +/// mihomo `/configs` PATCH 失败时返回 `Err`,以便命令层把失败上抛给前端。 +/// (此前该函数吞掉错误并始终视为成功,导致 UI 误判"切换成功"、看似"切不动"。) +pub async fn change_clash_mode(mode: String) -> Result<(), String> { let mut mapping = Mapping::new(); mapping.insert(Value::from("mode"), Value::from(mode.as_str())); // Convert YAML mapping to JSON Value @@ -88,27 +91,29 @@ pub async fn change_clash_mode(mode: String) { "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}"), + if let Err(err) = handle::Handle::mihomo().await.patch_base_config(&json_value).await { + logging!(error, Type::Core, "{err}"); + return Err(err.to_string().into()); } + + // 更新订阅 + 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(); + } + + Ok(()) } /// Test delay to a URL through proxy. diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 55156df32..a84d076fb 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -160,6 +160,7 @@ mod app_init { cmd::get_clash_info, cmd::patch_clash_config, cmd::patch_clash_mode, + cmd::get_clash_mode, cmd::change_clash_core, cmd::get_runtime_config, cmd::get_runtime_yaml,