diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 1e1d4a8e..86866e02 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -194,7 +194,10 @@ impl PrfItem { // 使用软件自己的代理 if self_proxy { - let port = Config::clash().data().get_mixed_port(); + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); let proxy_scheme = format!("http://127.0.0.1:{port}"); diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index f0c19699..64668839 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -93,6 +93,12 @@ pub struct IVerge { /// window size and position #[serde(skip_serializing_if = "Option::is_none")] pub window_size_position: Option>, + + /// 是否启用随机端口 + pub enable_random_port: Option, + + /// verge mixed port 用于覆盖 clash 的 mixed port + pub verge_mixed_port: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] @@ -139,6 +145,8 @@ impl IVerge { enable_auto_launch: Some(false), enable_silent_start: Some(false), enable_system_proxy: Some(false), + enable_random_port: Some(false), + verge_mixed_port: Some(7890), enable_proxy_guard: Some(false), proxy_guard_duration: Some(30), auto_close_connection: Some(true), @@ -177,6 +185,8 @@ impl IVerge { patch!(enable_service_mode); patch!(enable_auto_launch); patch!(enable_silent_start); + patch!(enable_random_port); + patch!(verge_mixed_port); patch!(enable_system_proxy); patch!(enable_proxy_guard); patch!(system_proxy_bypass); diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index b2f52552..821a42e1 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -27,7 +27,8 @@ static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;"; #[cfg(target_os = "linux")] static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,::1"; #[cfg(target_os = "macos")] -static DEFAULT_BYPASS: &str = "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,"; +static DEFAULT_BYPASS: &str = + "127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,"; impl Sysopt { pub fn global() -> &'static Sysopt { @@ -43,7 +44,10 @@ impl Sysopt { /// init the sysproxy pub fn init_sysproxy(&self) -> Result<()> { - let port = { Config::clash().latest().get_mixed_port() }; + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); let (enable, bypass) = { let verge = Config::verge(); @@ -284,7 +288,12 @@ impl Sysopt { log::debug!(target: "app", "try to guard the system proxy"); - let port = { Config::clash().latest().get_mixed_port() }; + let port = { + Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()) + }; let sysproxy = Sysproxy { enable: true, diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index 86c0109a..940e08d3 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -162,8 +162,13 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> { match { let mixed_port = patch.get("mixed-port"); - if mixed_port.is_some() { - let changed = mixed_port != Config::clash().data().0.get("mixed-port"); + let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false); + if mixed_port.is_some() && !enable_random_port { + let changed = mixed_port.clone().unwrap() + != Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); // 检查端口占用 if changed { if let Some(port) = mixed_port.clone().unwrap().as_u64() { @@ -333,11 +338,12 @@ async fn update_core_config() -> Result<()> { /// copy env variable pub fn copy_clash_env(option: &str) { - let port = { Config::clash().data().get_client_info().port }; + let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7890) }; let http_proxy = format!("http://127.0.0.1:{}", port); let socks5_proxy = format!("socks5://127.0.0.1:{}", port); - - let sh = format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"); + + let sh = + format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"); let cmd: String = format!("set http_proxy={http_proxy} \n set https_proxy={http_proxy}"); let ps: String = format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\""); diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 14d4bdd6..ccdc4f5e 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -1,8 +1,28 @@ +use crate::config::IVerge; use crate::{config::Config, core::*, utils::init, utils::server}; use crate::{log_err, trace_err}; use anyhow::Result; +use serde_yaml::Mapping; +use std::net::TcpListener; use tauri::{App, AppHandle, Manager}; +pub fn find_unused_port() -> Result { + match TcpListener::bind("127.0.0.1:0") { + Ok(listener) => { + let port = listener.local_addr()?.port(); + Ok(port) + } + Err(_) => { + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + log::warn!(target: "app", "use default port: {}", port); + Ok(port) + } + } +} + /// handle something when start app pub fn resolve_setup(app: &mut App) { #[cfg(target_os = "macos")] @@ -12,6 +32,33 @@ pub fn resolve_setup(app: &mut App) { log_err!(init::init_resources(app.package_info())); + // 处理随机端口 + let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false); + + let mut port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + + if enable_random_port { + port = find_unused_port().unwrap_or( + Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()), + ); + } + + Config::verge().data().patch_config(IVerge { + verge_mixed_port: Some(port), + ..IVerge::default() + }); + let _ = Config::verge().data().save_file(); + let mut mapping = Mapping::new(); + mapping.insert("mixed-port".into(), port.into()); + Config::clash().data().patch_config(mapping); + let _ = Config::clash().data().save_config(); + // 启动核心 log::trace!("init config"); log_err!(Config::init_config()); diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx index 20797405..209fb11a 100644 --- a/src/components/setting/mods/clash-port-viewer.tsx +++ b/src/components/setting/mods/clash-port-viewer.tsx @@ -4,30 +4,35 @@ import { useLockFn } from "ahooks"; import { List, ListItem, ListItemText, TextField } from "@mui/material"; import { useClashInfo } from "@/hooks/use-clash"; import { BaseDialog, DialogRef, Notice } from "@/components/base"; +import { useVerge } from "@/hooks/use-verge"; export const ClashPortViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const { clashInfo, patchInfo } = useClashInfo(); + const { verge, patchVerge } = useVerge(); const [open, setOpen] = useState(false); - const [port, setPort] = useState(clashInfo?.port ?? 7890); + const [port, setPort] = useState( + verge?.verge_mixed_port ?? clashInfo?.port ?? 7890 + ); useImperativeHandle(ref, () => ({ open: () => { - if (clashInfo?.port) setPort(clashInfo?.port); + if (verge?.verge_mixed_port) setPort(verge?.verge_mixed_port); setOpen(true); }, close: () => setOpen(false), })); const onSave = useLockFn(async () => { - if (port === clashInfo?.port) { + if (port === verge?.verge_mixed_port) { setOpen(false); return; } try { await patchInfo({ "mixed-port": port }); + await patchVerge({ verge_mixed_port: port }); setOpen(false); Notice.success("Change Clash port successfully!", 1000); } catch (err: any) { diff --git a/src/components/setting/setting-clash.tsx b/src/components/setting/setting-clash.tsx index c18373c8..b06731f1 100644 --- a/src/components/setting/setting-clash.tsx +++ b/src/components/setting/setting-clash.tsx @@ -7,9 +7,10 @@ import { MenuItem, Typography, IconButton, + Tooltip, } from "@mui/material"; -import { ArrowForward, Settings } from "@mui/icons-material"; -import { DialogRef } from "@/components/base"; +import { ArrowForward, Settings, Shuffle } from "@mui/icons-material"; +import { DialogRef, Notice } from "@/components/base"; import { useClash } from "@/hooks/use-clash"; import { GuardState } from "./mods/guard-state"; import { WebUIViewer } from "./mods/web-ui-viewer"; @@ -20,6 +21,7 @@ import { SettingList, SettingItem } from "./mods/setting-comp"; import { ClashCoreViewer } from "./mods/clash-core-viewer"; import { invoke_uwp_tool } from "@/services/cmds"; import getSystem from "@/utils/get-system"; +import { useVerge } from "@/hooks/use-verge"; const isWIN = getSystem() === "windows"; @@ -32,12 +34,11 @@ const SettingClash = ({ onError }: Props) => { const { clash, version, mutateClash, patchClash } = useClash(); - const { - ipv6, - "allow-lan": allowLan, - "log-level": logLevel, - "mixed-port": mixedPort, - } = clash ?? {}; + const { verge, mutateVerge, patchVerge } = useVerge(); + + const { ipv6, "allow-lan": allowLan, "log-level": logLevel } = clash ?? {}; + + const { enable_random_port = false, verge_mixed_port } = verge ?? {}; const webRef = useRef(null); const fieldRef = useRef(null); @@ -49,7 +50,9 @@ const SettingClash = ({ onError }: Props) => { const onChangeData = (patch: Partial) => { mutateClash((old) => ({ ...(old! || {}), ...patch }), false); }; - + const onChangeVerge = (patch: Partial) => { + mutateVerge({ ...verge, ...patch }, false); + }; return ( @@ -103,11 +106,32 @@ const SettingClash = ({ onError }: Props) => { - + + { + Notice.success(t("After restart to take effect"), 1000); + onChangeVerge({ enable_random_port: !enable_random_port }); + patchVerge({ enable_random_port: !enable_random_port }); + }} + > + + + + } + > { portRef.current?.open(); diff --git a/src/locales/en.json b/src/locales/en.json index d52dca46..a072cba7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -67,6 +67,8 @@ "IPv6": "IPv6", "Log Level": "Log Level", "Mixed Port": "Mixed Port", + "Random Port": "Random Port", + "After restart to take effect": "After restart to take effect", "External": "External", "Clash Core": "Clash Core", "Grant": "Grant", diff --git a/src/locales/ru.json b/src/locales/ru.json index 8ec50112..35488f94 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -64,6 +64,8 @@ "IPv6": "IPv6", "Log Level": "Уровень логов", "Mixed Port": "Смешанный порт", + "Random Port": "Случайный порт", + "After restart to take effect": "Чтобы изменения вступили в силу, необходимо перезапустить приложение", "Clash Core": "Ядро Clash", "Tun Mode": "Режим туннеля", "Service Mode": "Режим сервиса", diff --git a/src/locales/zh.json b/src/locales/zh.json index e6d28561..abfdcc46 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -67,6 +67,8 @@ "IPv6": "IPv6", "Log Level": "日志等级", "Mixed Port": "端口设置", + "Random Port": "随机端口", + "After restart to take effect": "重启后生效", "External": "外部控制", "Clash Core": "Clash 内核", "Grant": "授权", diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 49429f78..152b7cc4 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -166,6 +166,8 @@ interface IVergeConfig { enable_service_mode?: boolean; enable_silent_start?: boolean; enable_system_proxy?: boolean; + enable_random_port?: boolean; + verge_mixed_port?: number; enable_proxy_guard?: boolean; proxy_guard_duration?: number; system_proxy_bypass?: string;