diff --git a/Changelog.md b/Changelog.md index f32e0558b..08d52275f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ - 修复 macOS 有线网络 DNS 劫持失败 - 修复 Monaco 编辑器内右键菜单显示异常 +- 修复设置代理端口时检查端口占用
✨ 新增功能 diff --git a/src-tauri/src/cmd/network.rs b/src-tauri/src/cmd/network.rs index 3c9aee77b..5ba428dca 100644 --- a/src-tauri/src/cmd/network.rs +++ b/src-tauri/src/cmd/network.rs @@ -4,6 +4,7 @@ use clash_verge_logging::{Type, logging}; use gethostname::gethostname; use network_interface::NetworkInterface; use serde_yaml_ng::Mapping; +use std::net::TcpListener; use sysproxy::{Autoproxy, Sysproxy}; use tauri_plugin_clash_verge_sysinfo; @@ -95,3 +96,11 @@ pub fn get_network_interfaces_info() -> CmdResult> { Ok(result) } + +#[tauri::command] +pub fn is_port_in_use(port: u16) -> bool { + match TcpListener::bind(("127.0.0.1", port)) { + Ok(_listener) => false, + Err(_) => true, + } +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a81f292cd..08b3c358b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -137,6 +137,7 @@ mod app_init { tauri_plugin_clash_verge_sysinfo::commands::get_app_uptime, tauri_plugin_clash_verge_sysinfo::commands::app_is_admin, tauri_plugin_clash_verge_sysinfo::commands::export_diagnostic_info, + cmd::is_port_in_use, cmd::get_sys_proxy, cmd::get_auto_proxy, cmd::open_app_dir, diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx index a124e29a0..38ef1c0b4 100644 --- a/src/components/setting/mods/clash-port-viewer.tsx +++ b/src/components/setting/mods/clash-port-viewer.tsx @@ -9,12 +9,13 @@ import { TextField, } from "@mui/material"; import { useLockFn, useRequest } from "ahooks"; -import { forwardRef, useImperativeHandle, useState } from "react"; +import { forwardRef, useImperativeHandle, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { BaseDialog, Switch } from "@/components/base"; import { useClashInfo } from "@/hooks/use-clash"; import { useVerge } from "@/hooks/use-verge"; +import { isPortInUse } from "@/services/cmds"; import { showNotice } from "@/services/notice-service"; import getSystem from "@/utils/get-system"; @@ -59,6 +60,9 @@ export const ClashPortViewer = forwardRef((_, ref) => { verge?.verge_tproxy_enabled ?? false, ); + // 保存打开对话框时的原始值,用于在检测到端口被占用时恢复 + const originalPortsRef = useRef | null>(null); + // 添加保存请求,防止GUI卡死 const { loading, run: saveSettings } = useRequest( async (params: { clashConfig: any; vergeConfig: any }) => { @@ -82,15 +86,27 @@ export const ClashPortViewer = forwardRef((_, ref) => { useImperativeHandle(ref, () => ({ open: () => { - setMixedPort(verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897); - setSocksPort(verge?.verge_socks_port ?? 7898); - setSocksEnabled(verge?.verge_socks_enabled ?? false); - setHttpPort(verge?.verge_port ?? 7899); - setHttpEnabled(verge?.verge_http_enabled ?? false); - setRedirPort(verge?.verge_redir_port ?? 7895); - setRedirEnabled(verge?.verge_redir_enabled ?? false); - setTproxyPort(verge?.verge_tproxy_port ?? 7896); - setTproxyEnabled(verge?.verge_tproxy_enabled ?? false); + originalPortsRef.current = { + mixedPort: verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897, + socksPort: verge?.verge_socks_port ?? 7898, + socksEnabled: verge?.verge_socks_enabled ?? false, + httpPort: verge?.verge_port ?? 7899, + httpEnabled: verge?.verge_http_enabled ?? false, + redirPort: verge?.verge_redir_port ?? 7895, + redirEnabled: verge?.verge_redir_enabled ?? false, + tproxyPort: verge?.verge_tproxy_port ?? 7896, + tproxyEnabled: verge?.verge_tproxy_enabled ?? false, + }; + + setMixedPort(originalPortsRef.current.mixedPort); + setSocksPort(originalPortsRef.current.socksPort); + setSocksEnabled(originalPortsRef.current.socksEnabled); + setHttpPort(originalPortsRef.current.httpPort); + setHttpEnabled(originalPortsRef.current.httpEnabled); + setRedirPort(originalPortsRef.current.redirPort); + setRedirEnabled(originalPortsRef.current.redirEnabled); + setTproxyPort(originalPortsRef.current.tproxyPort); + setTproxyEnabled(originalPortsRef.current.tproxyEnabled); setOpen(true); }, close: () => setOpen(false), @@ -124,6 +140,47 @@ export const ClashPortViewer = forwardRef((_, ref) => { return; } + for (const port of portList) { + try { + const inUse = await isPortInUse(port); + if (inUse) { + showNotice.error("settings.modals.clashPort.messages.portInUse", { + port, + }); + const original = originalPortsRef.current; + if (original) { + setMixedPort(original.mixedPort); + setSocksPort(original.socksPort); + setSocksEnabled(original.socksEnabled); + setHttpPort(original.httpPort); + setHttpEnabled(original.httpEnabled); + setRedirPort(original.redirPort); + setRedirEnabled(original.redirEnabled); + setTproxyPort(original.tproxyPort); + setTproxyEnabled(original.tproxyEnabled); + } else { + setMixedPort( + verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897, + ); + setSocksPort(verge?.verge_socks_port ?? 7898); + setSocksEnabled(verge?.verge_socks_enabled ?? false); + setHttpPort(verge?.verge_port ?? 7899); + setHttpEnabled(verge?.verge_http_enabled ?? false); + setRedirPort(verge?.verge_redir_port ?? 7895); + setRedirEnabled(verge?.verge_redir_enabled ?? false); + setTproxyPort(verge?.verge_tproxy_port ?? 7896); + setTproxyEnabled(verge?.verge_tproxy_enabled ?? false); + } + return; + } + } catch (error) { + showNotice.error("settings.modals.clashPort.messages.portCheckFailed", { + error, + }); + return; + } + } + // 准备配置数据 const clashConfig = { "mixed-port": mixedPort, diff --git a/src/hooks/use-clash.ts b/src/hooks/use-clash.ts index 8ee79119a..34dfca9fd 100644 --- a/src/hooks/use-clash.ts +++ b/src/hooks/use-clash.ts @@ -4,8 +4,8 @@ import { getVersion } from "tauri-plugin-mihomo-api"; import { getClashInfo, - patchClashConfig, getRuntimeConfig, + patchClashConfig, } from "@/services/cmds"; const PORT_KEYS = [ diff --git a/src/locales/ar/settings.json b/src/locales/ar/settings.json index 528623bcf..fa0f10c00 100644 --- a/src/locales/ar/settings.json +++ b/src/locales/ar/settings.json @@ -252,6 +252,7 @@ "random": "منفذ عشوائي" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/de/settings.json b/src/locales/de/settings.json index 2b94147d5..647b768f2 100644 --- a/src/locales/de/settings.json +++ b/src/locales/de/settings.json @@ -252,6 +252,7 @@ "random": "Zufälliger Port" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json index dd5298fad..fd7e90047 100644 --- a/src/locales/en/settings.json +++ b/src/locales/en/settings.json @@ -252,6 +252,7 @@ "random": "Random Port" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json index 88fd88d43..8f3e7bea1 100644 --- a/src/locales/es/settings.json +++ b/src/locales/es/settings.json @@ -252,6 +252,7 @@ "random": "Puerto aleatorio" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/fa/settings.json b/src/locales/fa/settings.json index b23c91307..4be31a0f5 100644 --- a/src/locales/fa/settings.json +++ b/src/locales/fa/settings.json @@ -252,6 +252,7 @@ "random": "پورت تصادفی" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/id/settings.json b/src/locales/id/settings.json index 3fe7459b5..a18c3ce72 100644 --- a/src/locales/id/settings.json +++ b/src/locales/id/settings.json @@ -252,6 +252,7 @@ "random": "Port Acak" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/jp/settings.json b/src/locales/jp/settings.json index a735690f2..68636870f 100644 --- a/src/locales/jp/settings.json +++ b/src/locales/jp/settings.json @@ -252,6 +252,7 @@ "random": "ランダムポート" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/ko/settings.json b/src/locales/ko/settings.json index be368b72a..713be4dbe 100644 --- a/src/locales/ko/settings.json +++ b/src/locales/ko/settings.json @@ -252,6 +252,7 @@ "random": "임의 포트" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "포트 설정이 저장되었습니다", "saveFailed": "포트 설정 저장에 실패했습니다" } diff --git a/src/locales/ru/settings.json b/src/locales/ru/settings.json index 74144e473..0e708313a 100644 --- a/src/locales/ru/settings.json +++ b/src/locales/ru/settings.json @@ -252,6 +252,7 @@ "random": "Случайный порт" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/tr/settings.json b/src/locales/tr/settings.json index f0c4f8037..f8f2432c1 100644 --- a/src/locales/tr/settings.json +++ b/src/locales/tr/settings.json @@ -252,6 +252,7 @@ "random": "Rastgele Port" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/tt/settings.json b/src/locales/tt/settings.json index 9ba86f51e..3451304b4 100644 --- a/src/locales/tt/settings.json +++ b/src/locales/tt/settings.json @@ -252,6 +252,7 @@ "random": "Очраклы порт" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "Port settings saved", "saveFailed": "Failed to save port settings" } diff --git a/src/locales/zh/settings.json b/src/locales/zh/settings.json index 65a8708b9..d3846f3dc 100644 --- a/src/locales/zh/settings.json +++ b/src/locales/zh/settings.json @@ -252,6 +252,7 @@ "random": "随机端口" }, "messages": { + "portInUse": "端口 {{port}} 已被占用", "saved": "端口设置已保存", "saveFailed": "端口设置保存失败" } diff --git a/src/locales/zhtw/settings.json b/src/locales/zhtw/settings.json index b3ce2c903..3df1684ba 100644 --- a/src/locales/zhtw/settings.json +++ b/src/locales/zhtw/settings.json @@ -252,6 +252,7 @@ "random": "隨機連接埠" }, "messages": { + "portInUse": "Port {{port}} is already in use", "saved": "連結埠設定已儲存", "saveFailed": "連結埠設定儲存失敗" } diff --git a/src/services/cmds.ts b/src/services/cmds.ts index cd34ecfc6..a56af4397 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -554,3 +554,12 @@ export const isAdmin = async () => { export async function getNextUpdateTime(uid: string) { return invoke("get_next_update_time", { uid }); } + +export const isPortInUse = async (port: number) => { + try { + return await invoke("is_port_in_use", { port }); + } catch (error) { + console.error("检查端口使用状态失败:", error); + return false; + } +}; diff --git a/src/types/generated/i18n-keys.ts b/src/types/generated/i18n-keys.ts index d186e4f30..7dce9e0d9 100644 --- a/src/types/generated/i18n-keys.ts +++ b/src/types/generated/i18n-keys.ts @@ -453,6 +453,7 @@ export const translationKeys = [ "settings.modals.clashPort.fields.redir", "settings.modals.clashPort.fields.tproxy", "settings.modals.clashPort.actions.random", + "settings.modals.clashPort.messages.portInUse", "settings.modals.clashPort.messages.saved", "settings.modals.clashPort.messages.saveFailed", "settings.modals.clashCore.variants.release", diff --git a/src/types/generated/i18n-resources.ts b/src/types/generated/i18n-resources.ts index 9a29fd841..fcb48efe4 100644 --- a/src/types/generated/i18n-resources.ts +++ b/src/types/generated/i18n-resources.ts @@ -793,6 +793,7 @@ export interface TranslationResources { tproxy: string; }; messages: { + portInUse: string; saved: string; saveFailed: string; };