mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: enhance Windows proxy retrieval by using WinAPI for registry access
This commit is contained in:
@@ -96,6 +96,8 @@ winapi = { version = "0.3.9", features = [
|
|||||||
"winerror",
|
"winerror",
|
||||||
"tlhelp32",
|
"tlhelp32",
|
||||||
"processthreadsapi",
|
"processthreadsapi",
|
||||||
|
"winhttp",
|
||||||
|
"winreg",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
#[cfg(target_os = "linux")]
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::process::Command;
|
|
||||||
use tokio::time::{timeout, Duration};
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use anyhow::anyhow;
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
pub struct AsyncAutoproxy {
|
pub struct AsyncAutoproxy {
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
@@ -71,54 +73,96 @@ impl AsyncProxyQuery {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
|
async fn get_auto_proxy_impl() -> Result<AsyncAutoproxy> {
|
||||||
// Windows: 使用 netsh winhttp show proxy 命令
|
// Windows: 从注册表读取PAC配置
|
||||||
let output = Command::new("netsh")
|
tokio::task::spawn_blocking(move || -> Result<AsyncAutoproxy> {
|
||||||
.args(["winhttp", "show", "proxy"])
|
Self::get_pac_config_from_registry()
|
||||||
.output()
|
})
|
||||||
.await?;
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
if !output.status.success() {
|
#[cfg(target_os = "windows")]
|
||||||
return Ok(AsyncAutoproxy::default());
|
fn get_pac_config_from_registry() -> Result<AsyncAutoproxy> {
|
||||||
}
|
use std::ptr;
|
||||||
|
use winapi::shared::minwindef::{DWORD, HKEY};
|
||||||
|
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
|
||||||
|
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
unsafe {
|
||||||
log::debug!(target: "app", "netsh output: {}", stdout);
|
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>();
|
||||||
|
|
||||||
// 解析输出,查找 PAC 配置
|
let mut hkey: HKEY = ptr::null_mut();
|
||||||
for line in stdout.lines() {
|
let result =
|
||||||
let line = line.trim();
|
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
||||||
if line.starts_with("代理自动配置脚本") || line.starts_with("Proxy auto-config script")
|
|
||||||
{
|
if result != 0 {
|
||||||
// 修复:正确解析包含冒号的URL
|
log::debug!(target: "app", "无法打开注册表项");
|
||||||
// 格式: "代理自动配置脚本 : http://127.0.0.1:11233/commands/pac"
|
return Ok(AsyncAutoproxy::default());
|
||||||
// 或: "Proxy auto-config script : http://127.0.0.1:11233/commands/pac"
|
}
|
||||||
if let Some(colon_pos) = line.find(" : ") {
|
|
||||||
let url = line[colon_pos + 3..].trim();
|
// 1. 检查自动配置是否启用 (AutoConfigURL 存在且不为空即表示启用)
|
||||||
if !url.is_empty() && url != "(none)" && url != "无" {
|
let auto_config_url_name = "AutoConfigURL\0".encode_utf16().collect::<Vec<u16>>();
|
||||||
log::debug!(target: "app", "解析到PAC URL: {}", url);
|
let mut url_buffer = vec![0u16; 1024];
|
||||||
return Ok(AsyncAutoproxy {
|
let mut url_buffer_size: DWORD = (url_buffer.len() * 2) as DWORD;
|
||||||
enable: true,
|
let mut url_value_type: DWORD = 0;
|
||||||
url: url.to_string(),
|
|
||||||
});
|
let url_query_result = RegQueryValueExW(
|
||||||
}
|
hkey,
|
||||||
} else if let Some(colon_pos) = line.find(':') {
|
auto_config_url_name.as_ptr(),
|
||||||
// 兼容其他可能的格式
|
ptr::null_mut(),
|
||||||
let url = line[colon_pos + 1..].trim();
|
&mut url_value_type,
|
||||||
// 确保这不是URL中的协议部分
|
url_buffer.as_mut_ptr() as *mut u8,
|
||||||
if url.starts_with("http") && !url.is_empty() && url != "(none)" && url != "无"
|
&mut url_buffer_size,
|
||||||
{
|
);
|
||||||
log::debug!(target: "app", "解析到PAC URL (fallback): {}", url);
|
|
||||||
return Ok(AsyncAutoproxy {
|
let mut pac_url = String::new();
|
||||||
enable: true,
|
if url_query_result == 0 && url_value_type == REG_SZ && url_buffer_size > 0 {
|
||||||
url: url.to_string(),
|
let end_pos = url_buffer
|
||||||
});
|
.iter()
|
||||||
}
|
.position(|&x| x == 0)
|
||||||
|
.unwrap_or(url_buffer.len());
|
||||||
|
pac_url = String::from_utf16_lossy(&url_buffer[..end_pos]);
|
||||||
|
log::debug!(target: "app", "从注册表读取到PAC URL: {}", pac_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查自动检测设置是否启用
|
||||||
|
let auto_detect_name = "AutoDetect\0".encode_utf16().collect::<Vec<u16>>();
|
||||||
|
let mut auto_detect: DWORD = 0;
|
||||||
|
let mut detect_buffer_size: DWORD = 4;
|
||||||
|
let mut detect_value_type: DWORD = 0;
|
||||||
|
|
||||||
|
let detect_query_result = RegQueryValueExW(
|
||||||
|
hkey,
|
||||||
|
auto_detect_name.as_ptr(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
&mut detect_value_type,
|
||||||
|
&mut auto_detect as *mut DWORD as *mut u8,
|
||||||
|
&mut detect_buffer_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
|
||||||
|
// PAC 启用的条件:AutoConfigURL 不为空,或 AutoDetect 被启用
|
||||||
|
let pac_enabled = !pac_url.is_empty()
|
||||||
|
|| (detect_query_result == 0 && detect_value_type == REG_DWORD && auto_detect != 0);
|
||||||
|
|
||||||
|
if pac_enabled {
|
||||||
|
log::debug!(target: "app", "PAC配置启用: URL={}, AutoDetect={}", pac_url, auto_detect);
|
||||||
|
|
||||||
|
if pac_url.is_empty() && auto_detect != 0 {
|
||||||
|
pac_url = "auto-detect".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(AsyncAutoproxy {
|
||||||
|
enable: true,
|
||||||
|
url: pac_url,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log::debug!(target: "app", "PAC配置未启用");
|
||||||
|
Ok(AsyncAutoproxy::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!(target: "app", "未找到有效的PAC配置");
|
|
||||||
Ok(AsyncAutoproxy::default())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -142,7 +186,7 @@ impl AsyncProxyQuery {
|
|||||||
if line.contains("ProxyAutoConfigEnable") && line.contains("1") {
|
if line.contains("ProxyAutoConfigEnable") && line.contains("1") {
|
||||||
pac_enabled = true;
|
pac_enabled = true;
|
||||||
} else if line.contains("ProxyAutoConfigURLString") {
|
} else if line.contains("ProxyAutoConfigURLString") {
|
||||||
// 修复:正确解析包含冒号的URL
|
// 正确解析包含冒号的URL
|
||||||
// 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac"
|
// 格式: "ProxyAutoConfigURLString : http://127.0.0.1:11233/commands/pac"
|
||||||
if let Some(colon_pos) = line.find(" : ") {
|
if let Some(colon_pos) = line.find(" : ") {
|
||||||
pac_url = line[colon_pos + 3..].trim().to_string();
|
pac_url = line[colon_pos + 3..].trim().to_string();
|
||||||
@@ -213,57 +257,121 @@ impl AsyncProxyQuery {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
|
async fn get_system_proxy_impl() -> Result<AsyncSysproxy> {
|
||||||
let output = Command::new("netsh")
|
// Windows: 使用注册表直接读取代理设置
|
||||||
.args(["winhttp", "show", "proxy"])
|
tokio::task::spawn_blocking(move || -> Result<AsyncSysproxy> {
|
||||||
.output()
|
Self::get_system_proxy_from_registry()
|
||||||
.await?;
|
})
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
|
||||||
if !output.status.success() {
|
#[cfg(target_os = "windows")]
|
||||||
return Ok(AsyncSysproxy::default());
|
fn get_system_proxy_from_registry() -> Result<AsyncSysproxy> {
|
||||||
}
|
use std::ptr;
|
||||||
|
use winapi::shared::minwindef::{DWORD, HKEY};
|
||||||
|
use winapi::um::winnt::{KEY_READ, REG_DWORD, REG_SZ};
|
||||||
|
use winapi::um::winreg::{RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_CURRENT_USER};
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
unsafe {
|
||||||
log::debug!(target: "app", "netsh proxy output: {}", stdout);
|
let key_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\0"
|
||||||
|
.encode_utf16()
|
||||||
|
.collect::<Vec<u16>>();
|
||||||
|
|
||||||
let mut proxy_enabled = false;
|
let mut hkey: HKEY = ptr::null_mut();
|
||||||
let mut proxy_server = String::new();
|
let result =
|
||||||
let mut bypass_list = String::new();
|
RegOpenKeyExW(HKEY_CURRENT_USER, key_path.as_ptr(), 0, KEY_READ, &mut hkey);
|
||||||
|
|
||||||
for line in stdout.lines() {
|
if result != 0 {
|
||||||
let line = line.trim();
|
return Ok(AsyncSysproxy::default());
|
||||||
if line.starts_with("代理服务器") || line.starts_with("Proxy Server") {
|
|
||||||
if let Some(server_part) = line.split(':').nth(1) {
|
|
||||||
let server = server_part.trim();
|
|
||||||
if !server.is_empty() && server != "(none)" && server != "无" {
|
|
||||||
proxy_server = server.to_string();
|
|
||||||
proxy_enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if line.starts_with("绕过列表") || line.starts_with("Bypass List") {
|
|
||||||
if let Some(bypass_part) = line.split(':').nth(1) {
|
|
||||||
bypass_list = bypass_part.trim().to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if proxy_enabled && !proxy_server.is_empty() {
|
// 检查代理是否启用
|
||||||
// 解析服务器地址和端口
|
let proxy_enable_name = "ProxyEnable\0".encode_utf16().collect::<Vec<u16>>();
|
||||||
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
|
let mut proxy_enable: DWORD = 0;
|
||||||
let host = proxy_server[..colon_pos].to_string();
|
let mut buffer_size: DWORD = 4;
|
||||||
let port = proxy_server[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
|
let mut value_type: DWORD = 0;
|
||||||
(host, port)
|
|
||||||
|
let enable_result = RegQueryValueExW(
|
||||||
|
hkey,
|
||||||
|
proxy_enable_name.as_ptr(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
&mut value_type,
|
||||||
|
&mut proxy_enable as *mut DWORD as *mut u8,
|
||||||
|
&mut buffer_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
if enable_result != 0 || value_type != REG_DWORD || proxy_enable == 0 {
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
return Ok(AsyncSysproxy::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取代理服务器设置
|
||||||
|
let proxy_server_name = "ProxyServer\0".encode_utf16().collect::<Vec<u16>>();
|
||||||
|
let mut buffer = vec![0u16; 1024];
|
||||||
|
let mut buffer_size: DWORD = (buffer.len() * 2) as DWORD;
|
||||||
|
let mut value_type: DWORD = 0;
|
||||||
|
|
||||||
|
let server_result = RegQueryValueExW(
|
||||||
|
hkey,
|
||||||
|
proxy_server_name.as_ptr(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
&mut value_type,
|
||||||
|
buffer.as_mut_ptr() as *mut u8,
|
||||||
|
&mut buffer_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut proxy_server = String::new();
|
||||||
|
if server_result == 0 && value_type == REG_SZ && buffer_size > 0 {
|
||||||
|
let end_pos = buffer.iter().position(|&x| x == 0).unwrap_or(buffer.len());
|
||||||
|
proxy_server = String::from_utf16_lossy(&buffer[..end_pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取代理绕过列表
|
||||||
|
let proxy_override_name = "ProxyOverride\0".encode_utf16().collect::<Vec<u16>>();
|
||||||
|
let mut bypass_buffer = vec![0u16; 1024];
|
||||||
|
let mut bypass_buffer_size: DWORD = (bypass_buffer.len() * 2) as DWORD;
|
||||||
|
let mut bypass_value_type: DWORD = 0;
|
||||||
|
|
||||||
|
let override_result = RegQueryValueExW(
|
||||||
|
hkey,
|
||||||
|
proxy_override_name.as_ptr(),
|
||||||
|
ptr::null_mut(),
|
||||||
|
&mut bypass_value_type,
|
||||||
|
bypass_buffer.as_mut_ptr() as *mut u8,
|
||||||
|
&mut bypass_buffer_size,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut bypass_list = String::new();
|
||||||
|
if override_result == 0 && bypass_value_type == REG_SZ && bypass_buffer_size > 0 {
|
||||||
|
let end_pos = bypass_buffer
|
||||||
|
.iter()
|
||||||
|
.position(|&x| x == 0)
|
||||||
|
.unwrap_or(bypass_buffer.len());
|
||||||
|
bypass_list = String::from_utf16_lossy(&bypass_buffer[..end_pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
RegCloseKey(hkey);
|
||||||
|
|
||||||
|
if !proxy_server.is_empty() {
|
||||||
|
// 解析服务器地址和端口
|
||||||
|
let (host, port) = if let Some(colon_pos) = proxy_server.rfind(':') {
|
||||||
|
let host = proxy_server[..colon_pos].to_string();
|
||||||
|
let port = proxy_server[colon_pos + 1..].parse::<u16>().unwrap_or(8080);
|
||||||
|
(host, port)
|
||||||
|
} else {
|
||||||
|
(proxy_server, 8080)
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!(target: "app", "从注册表读取到代理设置: {}:{}, bypass: {}", host, port, bypass_list);
|
||||||
|
|
||||||
|
Ok(AsyncSysproxy {
|
||||||
|
enable: true,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
bypass: bypass_list,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
(proxy_server, 8080)
|
Ok(AsyncSysproxy::default())
|
||||||
};
|
}
|
||||||
|
|
||||||
Ok(AsyncSysproxy {
|
|
||||||
enable: true,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
bypass: bypass_list,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(AsyncSysproxy::default())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -526,16 +526,10 @@ impl EventDrivenProxyManager {
|
|||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
async fn execute_sysproxy_command(args: &[&str]) {
|
async fn execute_sysproxy_command(args: &[&str]) {
|
||||||
use crate::{core::handle::Handle, utils::dirs};
|
use crate::utils::dirs;
|
||||||
use tauri_plugin_shell::ShellExt;
|
#[allow(unused_imports)] // creation_flags必须
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
let app_handle = match Handle::global().app_handle() {
|
use tokio::process::Command;
|
||||||
Some(handle) => handle,
|
|
||||||
None => {
|
|
||||||
log::error!(target: "app", "获取应用句柄失败");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let binary_path = match dirs::service_path() {
|
let binary_path = match dirs::service_path() {
|
||||||
Ok(path) => path,
|
Ok(path) => path,
|
||||||
@@ -551,10 +545,9 @@ impl EventDrivenProxyManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shell = app_handle.shell();
|
let output = Command::new(sysproxy_exe)
|
||||||
let output = shell
|
|
||||||
.command(sysproxy_exe.as_path().to_str().unwrap())
|
|
||||||
.args(args)
|
.args(args)
|
||||||
|
.creation_flags(0x08000000) // CREATE_NO_WINDOW - 隐藏窗口
|
||||||
.output()
|
.output()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -562,6 +555,12 @@ impl EventDrivenProxyManager {
|
|||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
log::error!(target: "app", "执行sysproxy命令失败: {:?}", args);
|
log::error!(target: "app", "执行sysproxy命令失败: {:?}", args);
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
if !stderr.is_empty() {
|
||||||
|
log::error!(target: "app", "sysproxy错误输出: {}", stderr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::debug!(target: "app", "成功执行sysproxy命令: {:?}", args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use anyhow::{anyhow, Result};
|
|||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::{fs, path::Path, path::PathBuf};
|
use std::{fs, os::windows::process::CommandExt, path::Path, path::PathBuf};
|
||||||
|
|
||||||
/// Windows 下的开机启动文件夹路径
|
/// Windows 下的开机启动文件夹路径
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -59,6 +59,8 @@ pub fn create_shortcut() -> Result<()> {
|
|||||||
|
|
||||||
let output = std::process::Command::new("powershell")
|
let output = std::process::Command::new("powershell")
|
||||||
.args(["-Command", &powershell_command])
|
.args(["-Command", &powershell_command])
|
||||||
|
// 隐藏 PowerShell 窗口
|
||||||
|
.creation_flags(0x08000000) // CREATE_NO_WINDOW
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
|
.map_err(|e| anyhow!("执行 PowerShell 命令失败: {}", e))?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user