diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 70c55c45..955b7bd4 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5169,7 +5169,7 @@ dependencies = [ [[package]] name = "sysproxy" version = "0.3.0" -source = "git+https://github.com/zzzgydi/sysproxy-rs?branch=main#1402eba27022a2da7b91b3090d2f4936b6260f10" +source = "git+https://github.com/zzzgydi/sysproxy-rs?branch=main#24e8d46cb338a6a8e28742dea6ed993cd6450780" dependencies = [ "interfaces", "iptools", diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 49cc9009..c0e54ce6 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -8,7 +8,7 @@ use crate::{ret_err, wrap_err}; use anyhow::{Context, Result}; use serde_yaml::Mapping; use std::collections::{HashMap, VecDeque}; -use sysproxy::Sysproxy; +use sysproxy::{Autoproxy, Sysproxy}; use tauri::{api, Manager}; type CmdResult = Result; @@ -194,7 +194,6 @@ pub fn grant_permission(_core: String) -> CmdResult { #[tauri::command] pub fn get_sys_proxy() -> CmdResult { let current = wrap_err!(Sysproxy::get_system_proxy())?; - let mut map = Mapping::new(); map.insert("enable".into(), current.enable.into()); map.insert( @@ -206,6 +205,18 @@ pub fn get_sys_proxy() -> CmdResult { Ok(map) } +/// get the system proxy +#[tauri::command] +pub fn get_auto_proxy() -> CmdResult { + let current = wrap_err!(Autoproxy::get_auto_proxy())?; + + let mut map = Mapping::new(); + map.insert("enable".into(), current.enable.into()); + map.insert("url".into(), current.url.into()); + + Ok(map) +} + #[tauri::command] pub fn get_clash_logs() -> CmdResult> { Ok(logger::Logger::global().get_log()) diff --git a/src-tauri/src/config/mod.rs b/src-tauri/src/config/mod.rs index b246a760..2add27b2 100644 --- a/src-tauri/src/config/mod.rs +++ b/src-tauri/src/config/mod.rs @@ -13,3 +13,8 @@ pub use self::prfitem::*; pub use self::profiles::*; pub use self::runtime::*; pub use self::verge::*; + +pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) { + return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;" +} +"#; diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index fe1f21e5..940b3602 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -1,3 +1,4 @@ +use crate::config::DEFAULT_PAC; use crate::utils::{dirs, help}; use anyhow::Result; use log::LevelFilter; @@ -80,6 +81,12 @@ pub struct IVerge { /// proxy guard duration pub proxy_guard_duration: Option, + /// use pac mode + pub proxy_auto_config: Option, + + /// pac script content + pub pac_file_content: Option, + /// theme setting pub theme_setting: Option, @@ -211,6 +218,8 @@ impl IVerge { enable_auto_launch: Some(false), enable_silent_start: Some(false), enable_system_proxy: Some(false), + proxy_auto_config: Some(false), + pac_file_content: Some(DEFAULT_PAC.into()), enable_random_port: Some(false), #[cfg(not(target_os = "windows"))] verge_redir_port: Some(7895), @@ -290,6 +299,8 @@ impl IVerge { patch!(enable_proxy_guard); patch!(system_proxy_bypass); patch!(proxy_guard_duration); + patch!(proxy_auto_config); + patch!(pac_file_content); patch!(theme_setting); patch!(web_ui_list); diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index 23b608bc..af5dd25b 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -1,11 +1,14 @@ -use crate::{config::Config, log_err}; +use crate::{ + config::{Config, IVerge}, + log_err, +}; use anyhow::{anyhow, Result}; use auto_launch::{AutoLaunch, AutoLaunchBuilder}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use std::env::current_exe; use std::sync::Arc; -use sysproxy::Sysproxy; +use sysproxy::{Autoproxy, Sysproxy}; use tauri::async_runtime::Mutex as TokioMutex; pub struct Sysopt { @@ -16,6 +19,13 @@ pub struct Sysopt { /// recover it when exit old_sysproxy: Arc>>, + /// current auto proxy setting + cur_autoproxy: Arc>>, + + /// record the original auto proxy + /// recover it when exit + old_autoproxy: Arc>>, + /// helps to auto launch the app auto_launch: Arc>>, @@ -38,6 +48,8 @@ impl Sysopt { SYSOPT.get_or_init(|| Sysopt { cur_sysproxy: Arc::new(Mutex::new(None)), old_sysproxy: Arc::new(Mutex::new(None)), + cur_autoproxy: Arc::new(Mutex::new(None)), + old_autoproxy: Arc::new(Mutex::new(None)), auto_launch: Arc::new(Mutex::new(None)), guard_state: Arc::new(TokioMutex::new(false)), }) @@ -49,38 +61,77 @@ impl Sysopt { .latest() .verge_mixed_port .unwrap_or(Config::clash().data().get_mixed_port()); + let pac_port = IVerge::get_singleton_port(); - let (enable, bypass) = { + let (enable, bypass, pac) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.system_proxy_bypass.clone(), + verge.proxy_auto_config.unwrap_or(false), ) }; - - let current = Sysproxy { - enable, - host: String::from("127.0.0.1"), - port, - bypass: match bypass { - Some(bypass) => { - if bypass.is_empty() { - DEFAULT_BYPASS.into() - } else { - bypass + if pac { + let sys = Sysproxy { + enable: false, + host: String::from("127.0.0.1"), + port, + bypass: match bypass { + Some(bypass) => { + if bypass.is_empty() { + DEFAULT_BYPASS.into() + } else { + bypass + } } - } - None => DEFAULT_BYPASS.into(), - }, - }; - - if enable { + None => DEFAULT_BYPASS.into(), + }, + }; let old = Sysproxy::get_system_proxy().ok(); - current.set_system_proxy()?; + sys.set_system_proxy()?; *self.old_sysproxy.lock() = old; - *self.cur_sysproxy.lock() = Some(current); + *self.cur_sysproxy.lock() = Some(sys); + let auto = Autoproxy { + enable, + url: format!("http://127.0.0.1:{pac_port}/commands/pac"), + }; + let old = Autoproxy::get_auto_proxy().ok(); + auto.set_auto_proxy()?; + + *self.old_autoproxy.lock() = old; + *self.cur_autoproxy.lock() = Some(auto); + } else { + let auto = Autoproxy { + enable: false, + url: String::new(), + }; + let old = Autoproxy::get_auto_proxy().ok(); + auto.set_auto_proxy()?; + + *self.old_autoproxy.lock() = old; + *self.cur_autoproxy.lock() = Some(auto); + let sys = Sysproxy { + enable, + host: String::from("127.0.0.1"), + port, + bypass: match bypass { + Some(bypass) => { + if bypass.is_empty() { + DEFAULT_BYPASS.into() + } else { + bypass + } + } + None => DEFAULT_BYPASS.into(), + }, + }; + let old = Sysproxy::get_system_proxy().ok(); + sys.set_system_proxy()?; + + *self.old_sysproxy.lock() = old; + *self.cur_sysproxy.lock() = Some(sys); } // run the system proxy guard @@ -92,24 +143,38 @@ impl Sysopt { pub fn update_sysproxy(&self) -> Result<()> { let mut cur_sysproxy = self.cur_sysproxy.lock(); let old_sysproxy = self.old_sysproxy.lock(); + let mut cur_autoproxy = self.cur_autoproxy.lock(); + let old_autoproxy = self.old_autoproxy.lock(); - if cur_sysproxy.is_none() || old_sysproxy.is_none() { - drop(cur_sysproxy); - drop(old_sysproxy); - return self.init_sysproxy(); - } - - let (enable, bypass) = { + let (enable, bypass, pac) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.system_proxy_bypass.clone(), + verge.proxy_auto_config.unwrap_or(false), ) }; - let mut sysproxy = cur_sysproxy.take().unwrap(); + if pac { + if cur_autoproxy.is_none() || old_autoproxy.is_none() { + drop(cur_autoproxy); + drop(old_autoproxy); + return self.init_sysproxy(); + } + } else { + if cur_sysproxy.is_none() || old_sysproxy.is_none() { + drop(cur_sysproxy); + drop(old_sysproxy); + return self.init_sysproxy(); + } + } + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + let pac_port = IVerge::get_singleton_port(); - sysproxy.enable = enable; + let mut sysproxy = cur_sysproxy.take().unwrap(); sysproxy.bypass = match bypass { Some(bypass) => { if bypass.is_empty() { @@ -120,15 +185,26 @@ impl Sysopt { } None => DEFAULT_BYPASS.into(), }; - - let port = Config::verge() - .latest() - .verge_mixed_port - .unwrap_or(Config::clash().data().get_mixed_port()); sysproxy.port = port; - sysproxy.set_system_proxy()?; - *cur_sysproxy = Some(sysproxy); + let mut autoproxy = cur_autoproxy.take().unwrap(); + autoproxy.url = format!("http://127.0.0.1:{pac_port}/commands/pac"); + + if pac { + sysproxy.enable = false; + sysproxy.set_system_proxy()?; + *cur_sysproxy = Some(sysproxy); + autoproxy.enable = enable; + autoproxy.set_auto_proxy()?; + *cur_autoproxy = Some(autoproxy); + } else { + autoproxy.enable = false; + autoproxy.set_auto_proxy()?; + *cur_autoproxy = Some(autoproxy); + sysproxy.enable = enable; + sysproxy.set_system_proxy()?; + *cur_sysproxy = Some(sysproxy); + } Ok(()) } diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index bed5b8a0..f5903516 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -177,6 +177,8 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> { let tun_mode = patch.enable_tun_mode; let auto_launch = patch.enable_auto_launch; let system_proxy = patch.enable_system_proxy; + let pac = patch.proxy_auto_config; + let pac_content = patch.pac_file_content; let proxy_bypass = patch.system_proxy_bypass; let language = patch.language; let port = patch.verge_mixed_port; @@ -219,7 +221,12 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> { if auto_launch.is_some() { sysopt::Sysopt::global().update_launch()?; } - if system_proxy.is_some() || proxy_bypass.is_some() || port.is_some() { + if system_proxy.is_some() + || proxy_bypass.is_some() + || port.is_some() + || pac.is_some() + || pac_content.is_some() + { sysopt::Sysopt::global().update_sysproxy()?; sysopt::Sysopt::global().guard_proxy(); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 2eeb52ab..738135c6 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -36,6 +36,7 @@ fn main() -> std::io::Result<()> { .invoke_handler(tauri::generate_handler![ // common cmds::get_sys_proxy, + cmds::get_auto_proxy, cmds::open_app_dir, cmds::open_logs_dir, cmds::open_web_url, diff --git a/src-tauri/src/utils/server.rs b/src-tauri/src/utils/server.rs index cd1fb906..2d978d4c 100644 --- a/src-tauri/src/utils/server.rs +++ b/src-tauri/src/utils/server.rs @@ -1,7 +1,7 @@ extern crate warp; use super::resolve; -use crate::config::IVerge; +use crate::config::{Config, IVerge, DEFAULT_PAC}; use anyhow::{bail, Result}; use port_scanner::local_port_available; use std::convert::Infallible; @@ -64,6 +64,18 @@ pub fn embed_server(app_handle: AppHandle) { "ok" }); + let pac = warp::path!("commands" / "pac").map(move || { + let content = Config::verge() + .latest() + .pac_file_content + .clone() + .unwrap_or(DEFAULT_PAC.to_string()); + let port = Config::verge() + .latest() + .verge_mixed_port + .unwrap_or(Config::clash().data().get_mixed_port()); + content.replace("%mixed-port%", &format!("{}", port)) + }); let scheme = warp::path!("commands" / "scheme") .and(warp::query::()) .and_then(scheme_handler); @@ -72,7 +84,7 @@ pub fn embed_server(app_handle: AppHandle) { resolve::resolve_scheme(query.param).await; Ok("ok") } - let commands = ping.or(visible).or(scheme); + let commands = ping.or(visible).or(pac).or(scheme); warp::serve(commands).run(([127, 0, 0, 1], port)).await; }); } diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx index 8f2d94d6..7a991f01 100644 --- a/src/components/setting/mods/sysproxy-viewer.tsx +++ b/src/components/setting/mods/sysproxy-viewer.tsx @@ -10,23 +10,34 @@ import { styled, TextField, Typography, + Button, } from "@mui/material"; import { useVerge } from "@/hooks/use-verge"; -import { getSystemProxy } from "@/services/cmds"; +import { getSystemProxy, getAutotemProxy } from "@/services/cmds"; import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base"; +import { Edit } from "@mui/icons-material"; +import { EditorViewer } from "@/components/profile/editor-viewer"; +const DEFAULT_PAC = `function FindProxyForURL(url, host) { + return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;" +}`; export const SysproxyViewer = forwardRef((props, ref) => { const { t } = useTranslation(); const [open, setOpen] = useState(false); - + const [editorOpen, setEditorOpen] = useState(false); const { verge, patchVerge } = useVerge(); type SysProxy = Awaited>; const [sysproxy, setSysproxy] = useState(); + type AutoProxy = Awaited>; + const [autoproxy, setAutoproxy] = useState(); + const { enable_system_proxy: enabled, + proxy_auto_config, + pac_file_content, enable_proxy_guard, system_proxy_bypass, proxy_guard_duration, @@ -36,6 +47,8 @@ export const SysproxyViewer = forwardRef((props, ref) => { guard: enable_proxy_guard, bypass: system_proxy_bypass, duration: proxy_guard_duration ?? 10, + pac: proxy_auto_config, + pac_content: pac_file_content ?? DEFAULT_PAC, }); useImperativeHandle(ref, () => ({ @@ -45,8 +58,11 @@ export const SysproxyViewer = forwardRef((props, ref) => { guard: enable_proxy_guard, bypass: system_proxy_bypass, duration: proxy_guard_duration ?? 10, + pac: proxy_auto_config, + pac_content: pac_file_content ?? DEFAULT_PAC, }); getSystemProxy().then((p) => setSysproxy(p)); + getAutotemProxy().then((p) => setAutoproxy(p)); }, close: () => setOpen(false), })); @@ -68,6 +84,12 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (value.bypass !== system_proxy_bypass) { patch.system_proxy_bypass = value.bypass; } + if (value.pac !== proxy_auto_config) { + patch.proxy_auto_config = value.pac; + } + if (value.pac_content !== pac_file_content) { + patch.pac_file_content = value.pac_content; + } try { await patchVerge(patch); @@ -89,6 +111,15 @@ export const SysproxyViewer = forwardRef((props, ref) => { onOk={onSave} > + + + setValue((v) => ({ ...v, pac: e }))} + /> + ((props, ref) => { }} /> - - - - - - - setValue((v) => ({ ...v, bypass: e.target.value })) - } - /> - + {!value.pac && ( + <> + + + + + + setValue((v) => ({ ...v, bypass: e.target.value })) + } + /> + + + )} + {value.pac && ( + <> + + + + { + setValue((v) => ({ ...v, pac_content: content ?? "" })); + }} + onClose={() => { + setEditorOpen(false); + }} + /> + + + )} @@ -146,29 +215,42 @@ export const SysproxyViewer = forwardRef((props, ref) => { {t("Enable status")} - {(!!sysproxy?.enable).toString()} + {value.pac + ? (!!autoproxy?.enable).toString() + : (!!sysproxy?.enable).toString()} + {!value.pac && ( + <> + + {t("Server Addr")} + + {sysproxy?.server || "-"} + + - - {t("Server Addr")} - {sysproxy?.server || "-"} - - - - {t("Bypass")} - - - - + + {t("Bypass")} + + + + + + )} + {value.pac && ( + + {t("PAC URL")} + {autoproxy?.url || "-"} + + )} ); diff --git a/src/locales/en.json b/src/locales/en.json index 0aa249d6..d22672ca 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -118,6 +118,9 @@ "System Proxy Setting": "System Proxy Setting", "Open UWP tool": "Open UWP tool", "Update GeoData": "Update GeoData", + "Use PAC Mode": "Use PAC Mode", + "PAC URL": "PAC URL", + "PAC Script Content": "PAC Script Content", "Proxy Guard": "Proxy Guard", "Guard Duration": "Guard Duration", "Proxy Bypass": "Proxy Bypass", diff --git a/src/locales/fa.json b/src/locales/fa.json index bba17b33..11ed4627 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -118,6 +118,9 @@ "System Proxy Setting": "تنظیمات پراکسی سیستم", "Open UWP tool": "باز کردن ابزار UWP", "Update GeoData": "به‌روزرسانی GeoData", + "Use PAC Mode": "استفاده از حالت PAC", + "PAC URL": "PAC URL", + "PAC Script Content": "محتوای اسکریپت PAC", "Proxy Guard": "محافظ پراکسی", "Guard Duration": "مدت محافظت", "Proxy Bypass": "دور زدن پراکسی", diff --git a/src/locales/ru.json b/src/locales/ru.json index e4d7636a..0047d5c5 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -118,6 +118,9 @@ "System Proxy Setting": "Настройка системного прокси", "Open UWP tool": "Открыть UWP инструмент", "Update GeoData": "Обновление GeoData", + "Use PAC Mode": "Используйте режим PAC.", + "PAC URL": "Адрес PAC", + "PAC Script Content": "Содержание сценария PAC", "Proxy Guard": "Защита прокси", "Guard Duration": "Период защиты", "Proxy Bypass": "Игнорирование прокси", diff --git a/src/locales/zh.json b/src/locales/zh.json index 22cec44c..dc7ed0b6 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -118,6 +118,9 @@ "System Proxy Setting": "系统代理设置", "Open UWP tool": "UWP 工具", "Update GeoData": "更新 GeoData", + "Use PAC Mode": "使用PAC模式", + "PAC URL": "PAC 地址", + "PAC Script Content": "PAC 脚本内容", "Proxy Guard": "系统代理守卫", "Guard Duration": "代理守卫间隔", "Proxy Bypass": "代理绕过", diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 9b7ac1b9..b91ccec9 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -127,6 +127,13 @@ export async function getSystemProxy() { }>("get_sys_proxy"); } +export async function getAutotemProxy() { + return invoke<{ + enable: boolean; + url: string; + }>("get_auto_proxy"); +} + export async function changeClashCore(clashCore: string) { return invoke("change_clash_core", { clashCore }); } diff --git a/src/services/types.d.ts b/src/services/types.d.ts index 4ede695f..13734526 100644 --- a/src/services/types.d.ts +++ b/src/services/types.d.ts @@ -216,6 +216,8 @@ interface IVergeConfig { enable_service_mode?: boolean; enable_silent_start?: boolean; enable_system_proxy?: boolean; + proxy_auto_config?: boolean; + pac_file_content?: string; enable_random_port?: boolean; verge_mixed_port?: number; verge_socks_port?: number;