Forráskód Böngészése

feat: support random mixed port

WhizPanda 1 éve
szülő
commit
4906ca7059

+ 4 - 1
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}");
 

+ 10 - 0
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<Vec<f64>>,
+
+    /// 是否启用随机端口
+    pub enable_random_port: Option<bool>,
+
+    /// verge mixed port 用于覆盖 clash 的 mixed port
+    pub verge_mixed_port: Option<u16>,
 }
 
 #[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);

+ 12 - 3
src-tauri/src/core/sysopt.rs

@@ -27,7 +27,8 @@ static DEFAULT_BYPASS: &str = "localhost;127.*;192.168.*;10.*;172.16.*;<local>";
 #[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,<local>";
+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,<local>";
 
 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,

+ 11 - 5
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}\"");
 

+ 47 - 0
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<u16> {
+    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());

+ 8 - 3
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<DialogRef>((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) {

+ 35 - 11
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<DialogRef>(null);
   const fieldRef = useRef<DialogRef>(null);
@@ -49,7 +50,9 @@ const SettingClash = ({ onError }: Props) => {
   const onChangeData = (patch: Partial<IConfigData>) => {
     mutateClash((old) => ({ ...(old! || {}), ...patch }), false);
   };
-
+  const onChangeVerge = (patch: Partial<IVergeConfig>) => {
+    mutateVerge({ ...verge, ...patch }, false);
+  };
   return (
     <SettingList title={t("Clash Setting")}>
       <WebUIViewer ref={webRef} />
@@ -103,11 +106,32 @@ const SettingClash = ({ onError }: Props) => {
         </GuardState>
       </SettingItem>
 
-      <SettingItem label={t("Mixed Port")}>
+      <SettingItem
+        label={t("Mixed Port")}
+        extra={
+          <Tooltip title={t("Random Port")}>
+            <IconButton
+              color={enable_random_port ? "success" : "inherit"}
+              size="medium"
+              onClick={() => {
+                Notice.success(t("After restart to take effect"), 1000);
+                onChangeVerge({ enable_random_port: !enable_random_port });
+                patchVerge({ enable_random_port: !enable_random_port });
+              }}
+            >
+              <Shuffle
+                fontSize="inherit"
+                style={{ cursor: "pointer", opacity: 0.75 }}
+              />
+            </IconButton>
+          </Tooltip>
+        }
+      >
         <TextField
+          disabled={enable_random_port}
           autoComplete="off"
           size="small"
-          value={mixedPort ?? 0}
+          value={verge_mixed_port ?? 7890}
           sx={{ width: 100, input: { py: "7.5px", cursor: "pointer" } }}
           onClick={(e) => {
             portRef.current?.open();

+ 2 - 0
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",

+ 2 - 0
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": "Режим сервиса",

+ 2 - 0
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": "授权",

+ 2 - 0
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;