Files
clash-proxy/src-tauri/src/utils/resolve.rs

653 lines
22 KiB
Rust
Raw Normal View History

2025-03-03 02:27:45 +08:00
#[cfg(target_os = "macos")]
use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem},
core::*,
logging, logging_error,
module::lightweight,
process::AsyncHandler,
utils::{dirs, error, init, logging::Type, server},
wrap_err,
};
2024-09-13 03:21:55 +08:00
use anyhow::{bail, Result};
2023-12-15 15:18:01 +08:00
use once_cell::sync::OnceCell;
use parking_lot::{Mutex, RwLock};
2024-09-13 03:21:55 +08:00
use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use serde_json;
2023-12-01 12:56:18 +08:00
use serde_yaml::Mapping;
use std::{
net::TcpListener,
sync::Arc,
time::{Duration, Instant},
};
use tauri::{App, Emitter, Manager};
2024-09-13 03:21:55 +08:00
use tauri::Url;
2024-09-04 08:54:15 +08:00
//#[cfg(not(target_os = "linux"))]
2024-09-02 19:33:17 +08:00
// use window_shadows::set_shadow;
2023-12-15 15:18:01 +08:00
pub static VERSION: OnceCell<String> = OnceCell::new();
// 窗口状态文件中的尺寸
static STATE_WIDTH: OnceCell<u32> = OnceCell::new();
static STATE_HEIGHT: OnceCell<u32> = OnceCell::new();
// 定义默认窗口尺寸常量
const DEFAULT_WIDTH: u32 = 900;
const DEFAULT_HEIGHT: u32 = 700;
// 添加全局UI准备就绪标志
static UI_READY: OnceCell<Arc<RwLock<bool>>> = OnceCell::new();
// 窗口创建锁,防止并发创建窗口
static WINDOW_CREATING: OnceCell<Mutex<(bool, Instant)>> = OnceCell::new();
// 定义窗口状态结构体
#[derive(Debug, Serialize, Deserialize)]
struct WindowState {
width: Option<u32>,
height: Option<u32>,
}
fn get_window_creating_lock() -> &'static Mutex<(bool, Instant)> {
WINDOW_CREATING.get_or_init(|| Mutex::new((false, Instant::now())))
}
fn get_ui_ready() -> &'static Arc<RwLock<bool>> {
UI_READY.get_or_init(|| Arc::new(RwLock::new(false)))
}
// 标记UI已准备就绪
pub fn mark_ui_ready() {
let mut ready = get_ui_ready().write();
*ready = true;
}
// 重置UI就绪状态
pub fn reset_ui_ready() {
let mut ready = get_ui_ready().write();
*ready = false;
logging!(info, Type::Window, true, "UI就绪状态已重置");
}
2023-12-01 12:56:18 +08:00
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)
}
}
}
2021-12-27 02:29:28 +08:00
/// handle something when start app
pub async fn resolve_setup(app: &mut App) {
2024-09-12 07:59:51 +08:00
error::redirect_panic_to_log();
#[cfg(target_os = "macos")]
{
AppHandleManager::global().init(app.app_handle().clone());
AppHandleManager::global().set_activation_policy_accessory();
}
2023-12-15 15:18:01 +08:00
let version = app.package_info().version.to_string();
2022-11-14 01:26:33 +08:00
handle::Handle::global().init(app.app_handle());
2023-12-15 15:18:01 +08:00
VERSION.get_or_init(|| version.clone());
logging_error!(Type::Config, true, init::init_config());
logging_error!(Type::Setup, true, init::init_resources());
logging_error!(Type::Setup, true, init::init_scheme());
logging_error!(Type::Setup, true, init::startup_script().await);
2023-12-01 12:56:18 +08:00
// 处理随机端口
logging_error!(Type::System, true, resolve_random_port_config());
2022-11-14 01:26:33 +08:00
// 启动核心
logging!(trace, Type::Config, true, "Initial config");
logging_error!(Type::Config, true, Config::init_config().await);
2023-08-04 14:15:15 +08:00
logging!(trace, Type::Core, "Starting CoreManager");
logging_error!(Type::Core, true, CoreManager::global().init().await);
2022-11-14 01:26:33 +08:00
// setup a simple http server for singleton
log::trace!(target: "app", "launch embed server");
server::embed_server();
2021-12-27 02:29:28 +08:00
log::trace!(target: "app", "Initial system tray");
logging_error!(Type::Tray, true, tray::Tray::global().init());
logging_error!(Type::Tray, true, tray::Tray::global().create_systray(app));
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().update_sysproxy().await
);
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().init_guard_sysproxy()
);
let is_silent_start = { Config::verge().data().enable_silent_start }.unwrap_or(false);
create_window(!is_silent_start);
2022-11-14 01:26:33 +08:00
logging_error!(Type::System, true, timer::Timer::global().init());
let enable_auto_light_weight_mode =
{ Config::verge().data().enable_auto_light_weight_mode }.unwrap_or(false);
if enable_auto_light_weight_mode && !is_silent_start {
lightweight::enable_auto_light_weight_mode();
}
logging_error!(Type::Tray, true, tray::Tray::global().update_part());
// 初始化热键
logging!(trace, Type::System, true, "Initial hotkeys");
logging_error!(Type::System, true, hotkey::Hotkey::global().init());
2021-12-27 02:29:28 +08:00
}
/// reset system proxy (异步版)
pub async fn resolve_reset_async() {
#[cfg(target_os = "macos")]
logging!(info, Type::Tray, true, "Unsubscribing from traffic updates");
#[cfg(target_os = "macos")]
tray::Tray::global().unsubscribe_traffic();
logging_error!(
Type::System,
true,
sysopt::Sysopt::global().reset_sysproxy().await
);
logging_error!(Type::Core, true, CoreManager::global().stop_core().await);
#[cfg(target_os = "macos")]
{
logging!(info, Type::System, true, "Restoring system DNS settings");
restore_public_dns().await;
}
2021-12-27 02:29:28 +08:00
}
2022-02-20 23:46:13 +08:00
/// 窗口创建锁守卫
struct WindowCreateGuard;
impl Drop for WindowCreateGuard {
fn drop(&mut self) {
let mut lock = get_window_creating_lock().lock();
lock.0 = false;
logging!(info, Type::Window, true, "窗口创建过程已完成,释放锁");
}
}
2022-05-25 16:45:18 +08:00
/// create main window
pub fn create_window(is_showup: bool) {
// 尝试获取窗口创建锁
let mut creating_lock = get_window_creating_lock().lock();
let (is_creating, last_create_time) = *creating_lock;
let now = Instant::now();
// 检查是否有其他线程正在创建窗口,防止短时间内多次创建窗口导致竞态条件
if is_creating && now.duration_since(last_create_time) < Duration::from_secs(2) {
logging!(
warn,
Type::Window,
true,
"另一个窗口创建过程正在进行中,跳过本次创建请求"
);
return;
}
*creating_lock = (true, now);
drop(creating_lock);
// 创建窗口锁守卫结束时自动释放锁
let _guard = WindowCreateGuard;
// 打印 .window-state.json 文件路径
let window_state_file = dirs::app_home_dir()
.ok()
.map(|dir| dir.join(".window-state.json"));
logging!(
info,
Type::Window,
true,
"窗口状态文件路径: {:?}",
window_state_file
);
// 从文件加载窗口状态
if let Some(window_state_file_path) = window_state_file {
if window_state_file_path.exists() {
match std::fs::read_to_string(&window_state_file_path) {
Ok(content) => {
logging!(
debug,
Type::Window,
true,
"读取窗口状态文件内容成功: {} 字节",
content.len()
);
match serde_json::from_str::<WindowState>(&content) {
Ok(window_state) => {
logging!(
info,
Type::Window,
true,
"成功解析窗口状态: width={:?}, height={:?}",
window_state.width,
window_state.height
);
// 存储窗口状态以供后续使用
if let Some(width) = window_state.width {
STATE_WIDTH.set(width).ok();
}
if let Some(height) = window_state.height {
STATE_HEIGHT.set(height).ok();
}
}
Err(e) => {
logging!(error, Type::Window, true, "解析窗口状态文件失败: {:?}", e);
}
}
}
Err(e) => {
logging!(error, Type::Window, true, "读取窗口状态文件失败: {:?}", e);
}
}
} else {
logging!(
info,
Type::Window,
true,
"窗口状态文件不存在,将使用默认设置"
);
}
}
if !is_showup {
logging!(info, Type::Window, "Not to display create window");
return;
}
logging!(info, Type::Window, true, "Creating window");
let app_handle = handle::Handle::global().app_handle().unwrap();
2025-03-03 02:27:45 +08:00
#[cfg(target_os = "macos")]
AppHandleManager::global().set_activation_policy_regular();
// 检查是否从轻量模式恢复
let from_lightweight = crate::module::lightweight::is_in_lightweight_mode();
if from_lightweight {
logging!(info, Type::Window, true, "从轻量模式恢复窗口");
crate::module::lightweight::exit_lightweight_mode();
}
if let Some(window) = handle::Handle::global().get_window() {
logging!(info, Type::Window, true, "Found existing window");
2024-12-15 01:14:16 +08:00
if window.is_minimized().unwrap_or(false) {
let _ = window.unminimize();
}
if from_lightweight {
// 从轻量模式恢复需要销毁旧窗口以重建
logging!(info, Type::Window, true, "销毁旧窗口以重建新窗口");
let _ = window.close();
// 添加短暂延迟确保窗口正确关闭
std::thread::sleep(std::time::Duration::from_millis(100));
} else {
// 普通情况直接显示窗口
let _ = window.show();
let _ = window.set_focus();
return;
}
2022-11-12 11:37:23 +08:00
}
let width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
let height = STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
logging!(
info,
Type::Window,
true,
"Initializing new window with size: {}x{}",
width,
height
);
// 根据不同平台创建不同配置的窗口
2024-11-27 05:54:48 +08:00
#[cfg(target_os = "macos")]
let win_builder = {
// 基本配置
let builder = tauri::WebviewWindowBuilder::new(
&app_handle,
"main",
tauri::WebviewUrl::App("index.html".into()),
)
.title("Clash Verge")
.center()
.decorations(true)
.hidden_title(true) // 隐藏标题文本
.fullscreen(false)
.inner_size(width as f64, height as f64)
.min_inner_size(520.0, 520.0)
.visible(false);
// 尝试设置标题栏样式
// 注意根据Tauri版本不同此API可能有变化
// 如果编译出错,请注释掉下面这行
let builder = builder.title_bar_style(tauri::TitleBarStyle::Overlay);
builder
};
#[cfg(not(target_os = "macos"))]
let win_builder = tauri::WebviewWindowBuilder::new(
&app_handle,
"main",
2024-11-27 07:09:03 +08:00
tauri::WebviewUrl::App("index.html".into()),
)
2024-11-27 11:34:52 +08:00
.title("Clash Verge")
.center()
.fullscreen(false)
.inner_size(width as f64, height as f64)
.min_inner_size(520.0, 520.0)
.visible(false)
.decorations(false);
let window = win_builder.build();
match window {
Ok(window) => {
logging!(info, Type::Window, true, "Window created successfully");
// 静默启动模式等窗口初始化再启动自动进入轻量模式的计时监听器,防止初始化的时候找不到窗口对象导致监听器挂载失败
lightweight::run_once_auto_lightweight();
// 标记前端UI已准备就绪向前端发送启动完成事件
let app_handle_clone = app_handle.clone();
// 获取窗口创建后的初始大小
if let Ok(size) = window.inner_size() {
let state_width = STATE_WIDTH.get().copied().unwrap_or(DEFAULT_WIDTH);
let state_height = STATE_HEIGHT.get().copied().unwrap_or(DEFAULT_HEIGHT);
// 输出所有尺寸信息
logging!(
info,
Type::Window,
true,
"API报告的窗口尺寸: {}x{}, 状态文件尺寸: {}x{}, 默认尺寸: {}x{}",
size.width,
size.height,
state_width,
state_height,
DEFAULT_WIDTH,
DEFAULT_HEIGHT
);
// 优化窗口大小设置
if size.width < state_width || size.height < state_height {
logging!(
info,
Type::Window,
true,
"强制设置窗口尺寸: {}x{}",
state_width,
state_height
);
// 尝试不同的方式设置窗口大小
let _ = window.set_size(tauri::PhysicalSize {
width: state_width,
height: state_height,
});
// 关键:等待短暂时间让窗口尺寸生效
std::thread::sleep(std::time::Duration::from_millis(50));
// 再次检查窗口尺寸
if let Ok(new_size) = window.inner_size() {
logging!(
info,
Type::Window,
true,
"设置后API报告的窗口尺寸: {}x{}",
new_size.width,
new_size.height
);
}
}
}
// 标记此窗口是否从轻量模式恢复
let was_from_lightweight = from_lightweight;
AsyncHandler::spawn(move || async move {
// 处理启动完成
handle::Handle::global().mark_startup_completed();
if let Some(window) = app_handle_clone.get_webview_window("main") {
// 发送启动完成事件
let _ = window.emit("verge://startup-completed", ());
if is_showup {
let window_clone = window.clone();
// 从轻量模式恢复时使用较短的超时,避免卡死
let timeout_seconds = if was_from_lightweight {
// 从轻量模式恢复只等待2秒确保不会卡死
2
} else {
5
};
// 使用普通的等待方式替代事件监听,简化实现
let wait_result =
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
while !*get_ui_ready().read() {
tokio::time::sleep(Duration::from_millis(100)).await;
}
})
.await;
// 根据结果处理
match wait_result {
Ok(_) => {
logging!(info, Type::Window, true, "UI就绪显示窗口");
}
Err(_) => {
logging!(
warn,
Type::Window,
true,
"等待UI就绪超时({}秒),强制显示窗口",
timeout_seconds
);
// 强制设置UI就绪状态
*get_ui_ready().write() = true;
}
}
// 显示窗口
let _ = window_clone.show();
let _ = window_clone.set_focus();
logging!(info, Type::Window, true, "窗口创建和显示流程已完成");
}
}
});
}
Err(e) => {
logging!(error, Type::Window, true, "Failed to create window: {}", e);
}
}
2022-05-25 16:45:18 +08:00
}
pub async fn resolve_scheme(param: String) -> Result<()> {
log::info!(target:"app", "received deep link: {}", param);
2024-09-13 03:21:55 +08:00
let param_str = if param.starts_with("[") && param.len() > 4 {
param
.get(2..param.len() - 2)
.ok_or_else(|| anyhow::anyhow!("Invalid string slice boundaries"))?
} else {
param.as_str()
2024-09-13 03:21:55 +08:00
};
// 解析 URL
let link_parsed = match Url::parse(param_str) {
Ok(url) => url,
Err(e) => {
bail!("failed to parse deep link: {:?}, param: {:?}", e, param);
}
};
if link_parsed.scheme() == "clash" || link_parsed.scheme() == "clash-verge" {
let name = link_parsed
.query_pairs()
.find(|(key, _)| key == "name")
.map(|(_, value)| value.into_owned());
// 通过直接获取查询部分并解析特定参数来避免 URL 转义问题
let url_param = if let Some(query) = link_parsed.query() {
let prefix = "url=";
if let Some(pos) = query.find(prefix) {
let raw_url = &query[pos + prefix.len()..];
Some(percent_decode_str(raw_url).decode_utf8_lossy().to_string())
} else {
None
}
} else {
None
};
2024-09-13 03:21:55 +08:00
match url_param {
2024-09-13 03:21:55 +08:00
Some(url) => {
log::info!(target:"app", "decoded subscription url: {}", url);
create_window(false);
match PrfItem::from_url(url.as_ref(), name, None, None).await {
Ok(item) => {
let uid = item.uid.clone().unwrap();
let _ = wrap_err!(Config::profiles().data().append_item(item));
2024-12-08 15:54:46 +08:00
handle::Handle::notice_message("import_sub_url::ok", uid);
}
Err(e) => {
2024-12-08 15:54:46 +08:00
handle::Handle::notice_message("import_sub_url::error", e.to_string());
2024-09-13 03:21:55 +08:00
}
}
2024-09-02 19:33:17 +08:00
}
2024-09-13 03:21:55 +08:00
None => bail!("failed to get profile url"),
}
}
2024-09-13 03:21:55 +08:00
Ok(())
}
2024-10-09 01:14:03 +08:00
fn resolve_random_port_config() -> Result<()> {
let verge_config = Config::verge();
let clash_config = Config::clash();
let enable_random_port = verge_config.latest().enable_random_port.unwrap_or(false);
let default_port = verge_config
.latest()
.verge_mixed_port
.unwrap_or(clash_config.data().get_mixed_port());
let port = if enable_random_port {
find_unused_port().unwrap_or(default_port)
} else {
default_port
};
verge_config.data().patch_config(IVerge {
verge_mixed_port: Some(port),
..IVerge::default()
});
verge_config.data().save_file()?;
let mut mapping = Mapping::new();
mapping.insert("mixed-port".into(), port.into());
clash_config.data().patch_config(mapping);
clash_config.data().save_config()?;
Ok(())
}
#[cfg(target_os = "macos")]
pub async fn set_public_dns(dns_server: String) {
use crate::{core::handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to set system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("set_dns.sh");
if !script.exists() {
log::error!(target: "app", "set_dns.sh not found");
return;
}
let script = script.to_string_lossy().into_owned();
match app_handle
.shell()
.command("bash")
.args([script, dns_server])
.current_dir(resource_dir)
.status()
.await
{
Ok(status) => {
if status.success() {
log::info!(target: "app", "set system dns successfully");
} else {
let code = status.code().unwrap_or(-1);
log::error!(target: "app", "set system dns failed: {code}");
}
}
Err(err) => {
log::error!(target: "app", "set system dns failed: {err}");
}
}
}
#[cfg(target_os = "macos")]
pub async fn restore_public_dns() {
use crate::{core::handle, utils::dirs};
use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to unset system dns");
let resource_dir = dirs::app_resources_dir().unwrap();
let script = resource_dir.join("unset_dns.sh");
if !script.exists() {
log::error!(target: "app", "unset_dns.sh not found");
return;
}
let script = script.to_string_lossy().into_owned();
match app_handle
.shell()
.command("bash")
.args([script])
.current_dir(resource_dir)
.status()
.await
{
Ok(status) => {
if status.success() {
log::info!(target: "app", "unset system dns successfully");
} else {
let code = status.code().unwrap_or(-1);
log::error!(target: "app", "unset system dns failed: {code}");
}
}
Err(err) => {
log::error!(target: "app", "unset system dns failed: {err}");
}
}
}