From cb6b21ef982c271d5abf98b0da2b913b917c117c Mon Sep 17 00:00:00 2001 From: zyj Date: Tue, 10 Mar 2026 11:55:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=20proxy.go=20?= =?UTF-8?q?=E4=B8=BA=E5=B9=B3=E5=8F=B0=E7=89=B9=E5=AE=9A=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8C=E5=85=BC=E5=AE=B9=20macOS=20=E5=92=8C=20Linux\n\n-=20?= =?UTF-8?q?proxy.go:=20=E4=BF=9D=E7=95=99=E9=80=9A=E7=94=A8=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=92=8C=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0\n-=20pro?= =?UTF-8?q?xy=5Fwindows.go:=20=E9=80=9A=E8=BF=87=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E8=A1=A8=E8=AF=BB=E5=8F=96=20Windows=20=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E4=BB=A3=E7=90=86\n-=20proxy=5Fdarwin.go:=20=E9=80=9A=E8=BF=87?= =?UTF-8?q?=20scutil=20--proxy=20=E8=AF=BB=E5=8F=96=20macOS=20=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=BB=A3=E7=90=86\n-=20proxy=5Flinux.go:=20=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20gsettings/kreadconfig=20=E8=AF=BB=E5=8F=96=20Linux?= =?UTF-8?q?=20=E7=B3=BB=E7=BB=9F=E4=BB=A3=E7=90=86"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/proxy.go | 65 ++++---------------- internal/proxy_darwin.go | 82 +++++++++++++++++++++++++ internal/proxy_linux.go | 122 ++++++++++++++++++++++++++++++++++++++ internal/proxy_windows.go | 43 ++++++++++++++ 4 files changed, 259 insertions(+), 53 deletions(-) create mode 100644 internal/proxy_darwin.go create mode 100644 internal/proxy_linux.go create mode 100644 internal/proxy_windows.go diff --git a/internal/proxy.go b/internal/proxy.go index 5d9f6cf..0c6b5d6 100644 --- a/internal/proxy.go +++ b/internal/proxy.go @@ -4,8 +4,6 @@ import ( "fmt" "os" "strings" - - "golang.org/x/sys/windows/registry" ) // ProxyInfo 保存当前系统代理信息 @@ -25,8 +23,8 @@ type ProxyInfo struct { } // GetCurrentProxy 获取本机当前的代理设置 -// 优先读取 Windows 系统代理(注册表),同时也读取环境变量中的代理配置 -// 支持检测 Clash、V2Ray 等常见代理工具的配置 +// 优先读取系统代理(Windows 注册表 / macOS scutil / Linux 环境变量), +// 同时也读取环境变量中的代理配置 func GetCurrentProxy() (*ProxyInfo, error) { info := &ProxyInfo{} @@ -34,9 +32,8 @@ func GetCurrentProxy() (*ProxyInfo, error) { info.HTTPProxy = getEnvProxy("HTTP_PROXY", "http_proxy") info.HTTPSProxy = getEnvProxy("HTTPS_PROXY", "https_proxy") - // 2. 从 Windows 注册表读取系统代理设置 - if err := readWindowsSystemProxy(info); err != nil { - // 注册表读取失败不算致命错误,环境变量中可能仍有代理信息 + // 2. 从系统设置读取代理(平台相关) + if err := readSystemProxy(info); err != nil { fmt.Printf("读取系统代理设置时出错: %v\n", err) } @@ -54,42 +51,6 @@ func GetCurrentProxy() (*ProxyInfo, error) { return info, nil } -// readWindowsSystemProxy 从 Windows 注册表读取系统代理设置 -func readWindowsSystemProxy(info *ProxyInfo) error { - key, err := registry.OpenKey( - registry.CURRENT_USER, - `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, - registry.QUERY_VALUE, - ) - if err != nil { - return fmt.Errorf("无法打开注册表键: %w", err) - } - defer key.Close() - - // 读取代理是否启用 (ProxyEnable: 0=关闭, 1=开启) - proxyEnable, _, err := key.GetIntegerValue("ProxyEnable") - if err != nil { - return fmt.Errorf("无法读取 ProxyEnable: %w", err) - } - info.Enabled = proxyEnable == 1 - - // 读取代理服务器地址 - proxyServer, _, err := key.GetStringValue("ProxyServer") - if err == nil && proxyServer != "" { - info.Server = proxyServer - // 根据端口推断代理协议 - info.Protocol = guessProtocol(proxyServer) - } - - // 读取代理绕过列表 - bypass, _, err := key.GetStringValue("ProxyOverride") - if err == nil { - info.Bypass = bypass - } - - return nil -} - // getEnvProxy 依次检查多个环境变量名,返回第一个非空值 func getEnvProxy(names ...string) string { for _, name := range names { @@ -104,11 +65,9 @@ func getEnvProxy(names ...string) string { // 例如 "http://127.0.0.1:7890" -> "127.0.0.1:7890" func extractServerFromURL(proxyURL string) string { s := proxyURL - // 去掉协议前缀 if idx := strings.Index(s, "://"); idx != -1 { s = s[idx+3:] } - // 去掉尾部斜杠 s = strings.TrimRight(s, "/") return s } @@ -127,14 +86,14 @@ func extractProtocolFromURL(proxyURL string) string { // V2Ray 默认: 10808(socks5), 10809(http) func guessProtocol(server string) string { knownPorts := map[string]string{ - "7890": "http", // Clash HTTP - "7891": "socks5", // Clash SOCKS5 - "7892": "http", // Clash mixed - "10808": "socks5", // V2Ray SOCKS5 - "10809": "http", // V2Ray HTTP - "1080": "socks5", // 通用 SOCKS5 - "1081": "http", // 通用 HTTP - "8080": "http", // 通用 HTTP + "7890": "http", + "7891": "socks5", + "7892": "http", + "10808": "socks5", + "10809": "http", + "1080": "socks5", + "1081": "http", + "8080": "http", } parts := strings.Split(server, ":") diff --git a/internal/proxy_darwin.go b/internal/proxy_darwin.go new file mode 100644 index 0000000..a88b39a --- /dev/null +++ b/internal/proxy_darwin.go @@ -0,0 +1,82 @@ +//go:build darwin + +package internal + +import ( + "fmt" + "os/exec" + "strings" +) + +// readSystemProxy 从 macOS 系统偏好设置读取代理配置 +// 使用 scutil --proxy 命令获取系统代理信息 +func readSystemProxy(info *ProxyInfo) error { + out, err := exec.Command("scutil", "--proxy").Output() + if err != nil { + return fmt.Errorf("执行 scutil --proxy 失败: %w", err) + } + + lines := strings.Split(string(out), "\n") + settings := make(map[string]string) + for _, line := range lines { + line = strings.TrimSpace(line) + parts := strings.SplitN(line, " : ", 2) + if len(parts) == 2 { + settings[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + } + + // 检查 HTTP 代理 + if settings["HTTPEnable"] == "1" { + host := settings["HTTPProxy"] + port := settings["HTTPPort"] + if host != "" { + info.Enabled = true + info.Server = host + if port != "" && port != "0" { + info.Server = host + ":" + port + } + info.Protocol = "http" + } + } + + // 检查 HTTPS 代理 + if !info.Enabled && settings["HTTPSEnable"] == "1" { + host := settings["HTTPSProxy"] + port := settings["HTTPSPort"] + if host != "" { + info.Enabled = true + info.Server = host + if port != "" && port != "0" { + info.Server = host + ":" + port + } + info.Protocol = "http" + } + } + + // 检查 SOCKS 代理 + if !info.Enabled && settings["SOCKSEnable"] == "1" { + host := settings["SOCKSProxy"] + port := settings["SOCKSPort"] + if host != "" { + info.Enabled = true + info.Server = host + if port != "" && port != "0" { + info.Server = host + ":" + port + } + info.Protocol = "socks5" + } + } + + // 如果通过以上方式检测到了代理,根据端口再次猜测协议 + if info.Enabled && info.Server != "" { + info.Protocol = guessProtocol(info.Server) + } + + // 读取绕过列表 + if exceptions, ok := settings["ExceptionsList"]; ok { + info.Bypass = exceptions + } + + return nil +} diff --git a/internal/proxy_linux.go b/internal/proxy_linux.go new file mode 100644 index 0000000..8a0076c --- /dev/null +++ b/internal/proxy_linux.go @@ -0,0 +1,122 @@ +//go:build linux + +package internal + +import ( + "fmt" + "os/exec" + "strings" +) + +// readSystemProxy 从 Linux 系统读取代理配置 +// 优先尝试 GNOME (gsettings),失败则回退到环境变量 +func readSystemProxy(info *ProxyInfo) error { + // 尝试通过 gsettings 读取 GNOME 系统代理 + if err := readGnomeProxy(info); err == nil && info.Enabled { + return nil + } + + // 尝试通过 KDE 配置读取代理 + if err := readKDEProxy(info); err == nil && info.Enabled { + return nil + } + + // Linux 上环境变量已由 GetCurrentProxy 中读取,此处无需额外操作 + return nil +} + +// readGnomeProxy 通过 gsettings 读取 GNOME 桌面环境的代理配置 +func readGnomeProxy(info *ProxyInfo) error { + // 检查 gsettings 是否可用 + if _, err := exec.LookPath("gsettings"); err != nil { + return fmt.Errorf("gsettings 不可用: %w", err) + } + + // 读取代理模式 + mode, err := gsettingsGet("org.gnome.system.proxy", "mode") + if err != nil { + return err + } + + if mode != "'manual'" { + return nil // 代理未启用 + } + + // 读取 HTTP 代理 + host, err := gsettingsGet("org.gnome.system.proxy.http", "host") + if err == nil && host != "" && host != "''" { + host = strings.Trim(host, "'") + port, _ := gsettingsGet("org.gnome.system.proxy.http", "port") + port = strings.TrimSpace(port) + if port != "" && port != "0" { + info.Enabled = true + info.Server = host + ":" + port + info.Protocol = guessProtocol(info.Server) + return nil + } + } + + // 读取 SOCKS 代理 + host, err = gsettingsGet("org.gnome.system.proxy.socks", "host") + if err == nil && host != "" && host != "''" { + host = strings.Trim(host, "'") + port, _ := gsettingsGet("org.gnome.system.proxy.socks", "port") + port = strings.TrimSpace(port) + if port != "" && port != "0" { + info.Enabled = true + info.Server = host + ":" + port + info.Protocol = "socks5" + return nil + } + } + + return nil +} + +// readKDEProxy 通过 kreadconfig5/kreadconfig6 读取 KDE 代理配置 +func readKDEProxy(info *ProxyInfo) error { + // 尝试 kreadconfig5 或 kreadconfig6 + var cmd string + if _, err := exec.LookPath("kreadconfig6"); err == nil { + cmd = "kreadconfig6" + } else if _, err := exec.LookPath("kreadconfig5"); err == nil { + cmd = "kreadconfig5" + } else { + return fmt.Errorf("kreadconfig 不可用") + } + + // 读取代理类型 + proxyType, err := kdeGet(cmd, "Proxy Settings", "ProxyType") + if err != nil || proxyType != "1" { + return nil // 0 = 无代理, 1 = 手动 + } + + // 读取 HTTP 代理 + httpProxy, err := kdeGet(cmd, "Proxy Settings", "httpProxy") + if err == nil && httpProxy != "" { + info.Enabled = true + info.Server = extractServerFromURL(httpProxy) + info.Protocol = guessProtocol(info.Server) + return nil + } + + return nil +} + +// gsettingsGet 执行 gsettings get 命令 +func gsettingsGet(schema, key string) (string, error) { + out, err := exec.Command("gsettings", "get", schema, key).Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(out)), nil +} + +// kdeGet 执行 kreadconfig 命令读取 KDE 配置 +func kdeGet(cmd, group, key string) (string, error) { + out, err := exec.Command(cmd, "--file", "kioslaverc", "--group", group, "--key", key).Output() + if err != nil { + return "", err + } + return strings.TrimSpace(string(out)), nil +} diff --git a/internal/proxy_windows.go b/internal/proxy_windows.go new file mode 100644 index 0000000..fea3b31 --- /dev/null +++ b/internal/proxy_windows.go @@ -0,0 +1,43 @@ +package internal + +import ( + "fmt" + + "golang.org/x/sys/windows/registry" +) + +// readSystemProxy 从 Windows 注册表读取系统代理设置 +func readSystemProxy(info *ProxyInfo) error { + key, err := registry.OpenKey( + registry.CURRENT_USER, + `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, + registry.QUERY_VALUE, + ) + if err != nil { + return fmt.Errorf("无法打开注册表键: %w", err) + } + defer key.Close() + + // 读取代理是否启用 (ProxyEnable: 0=关闭, 1=开启) + proxyEnable, _, err := key.GetIntegerValue("ProxyEnable") + if err != nil { + return fmt.Errorf("无法读取 ProxyEnable: %w", err) + } + info.Enabled = proxyEnable == 1 + + // 读取代理服务器地址 + proxyServer, _, err := key.GetStringValue("ProxyServer") + if err == nil && proxyServer != "" { + info.Server = proxyServer + // 根据端口推断代理协议 + info.Protocol = guessProtocol(proxyServer) + } + + // 读取代理绕过列表 + bypass, _, err := key.GetStringValue("ProxyOverride") + if err == nil { + info.Bypass = bypass + } + + return nil +}