chore: git hooks for linter and formatter

This commit is contained in:
Tunglies 2025-03-13 12:51:20 +08:00
parent 124934b012
commit b57c6e408a
50 changed files with 479 additions and 375 deletions

View File

@ -1 +1,16 @@
#!/bin/bash
pnpm pretty-quick --staged pnpm pretty-quick --staged
# 运行 clippy fmt
cargo fmt --manifest-path ./src-tauri/Cargo.toml
if [ $? -ne 0 ]; then
echo "rustfmt failed to format the code. Please fix the issues and try again."
exit 1
fi
git add .
# 允许提交
exit 0

13
.husky/pre-push Normal file
View File

@ -0,0 +1,13 @@
#!/bin/bash
# 运行 clippy
cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
# 如果 clippy 失败,阻止 push
if [ $? -ne 0 ]; then
echo "Clippy found issues in sub_crate. Please fix them before pushing."
exit 1
fi
# 允许 push
exit 0

1
src-tauri/.clippy.toml Normal file
View File

@ -0,0 +1 @@
avoid-breaking-exported-api = true

View File

@ -93,7 +93,8 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
let response = wrap_err!(reqwest::get(&url).await)?; let response = wrap_err!(reqwest::get(&url).await)?;
// 检查内容类型是否为图片 // 检查内容类型是否为图片
let content_type = response.headers() let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE) .get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok()) .and_then(|v| v.to_str().ok())
.unwrap_or(""); .unwrap_or("");
@ -104,10 +105,10 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
let content = wrap_err!(response.bytes().await)?; let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面) // 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15 && let is_html = content.len() > 15
(content.starts_with(b"<!DOCTYPE html") || && (content.starts_with(b"<!DOCTYPE html")
content.starts_with(b"<html") || || content.starts_with(b"<html")
content.starts_with(b"<?xml")); || content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存 // 只有当内容确实是图片时才保存
if is_image && !is_html { if is_image && !is_html {
@ -129,7 +130,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件 // 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() { if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) { match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}, Ok(_) => {}
Err(_) => { Err(_) => {
let _ = std::fs::remove_file(&temp_path); let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() { if icon_path.exists() {
@ -144,7 +145,7 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
Ok(icon_path.to_string_lossy().to_string()) Ok(icon_path.to_string_lossy().to_string())
} else { } else {
let _ = std::fs::remove_file(&temp_path); let _ = std::fs::remove_file(&temp_path);
Err(format!("下载的内容不是有效图片: {}", url).into()) Err(format!("下载的内容不是有效图片: {}", url))
} }
} }
@ -158,8 +159,7 @@ pub struct IconInfo {
/// 复制图标文件 /// 复制图标文件
#[tauri::command] #[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> { pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::fs; use std::{fs, path::Path};
use std::path::Path;
let file_path = Path::new(&path); let file_path = Path::new(&path);

View File

@ -24,7 +24,8 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
/// 修改Clash模式 /// 修改Clash模式
#[tauri::command] #[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult { pub async fn patch_clash_mode(payload: String) -> CmdResult {
Ok(feat::change_clash_mode(payload)) feat::change_clash_mode(payload);
Ok(())
} }
/// 切换Clash核心 /// 切换Clash核心
@ -98,9 +99,11 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置 /// 应用或撤销DNS配置
#[tauri::command] #[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult { pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::config::Config; use crate::{
use crate::core::{handle, CoreManager}; config::Config,
use crate::utils::dirs; core::{handle, CoreManager},
utils::dirs,
};
use tauri::async_runtime; use tauri::async_runtime;
// 使用spawn来处理异步操作 // 使用spawn来处理异步操作

View File

@ -4,29 +4,29 @@ use anyhow::Result;
pub type CmdResult<T = ()> = Result<T, String>; pub type CmdResult<T = ()> = Result<T, String>;
// Command modules // Command modules
pub mod profile;
pub mod validate;
pub mod uwp;
pub mod webdav;
pub mod app; pub mod app;
pub mod network;
pub mod clash; pub mod clash;
pub mod verge; pub mod network;
pub mod profile;
pub mod proxy;
pub mod runtime; pub mod runtime;
pub mod save_profile; pub mod save_profile;
pub mod system; pub mod system;
pub mod proxy; pub mod uwp;
pub mod validate;
pub mod verge;
pub mod webdav;
// Re-export all command functions for backwards compatibility // Re-export all command functions for backwards compatibility
pub use profile::*;
pub use validate::*;
pub use uwp::*;
pub use webdav::*;
pub use app::*; pub use app::*;
pub use network::*;
pub use clash::*; pub use clash::*;
pub use verge::*; pub use network::*;
pub use profile::*;
pub use proxy::*;
pub use runtime::*; pub use runtime::*;
pub use save_profile::*; pub use save_profile::*;
pub use system::*; pub use system::*;
pub use proxy::*; pub use uwp::*;
pub use validate::*;
pub use verge::*;
pub use webdav::*;

View File

@ -1,8 +1,8 @@
use crate::wrap_err;
use super::CmdResult; use super::CmdResult;
use sysproxy::{Autoproxy, Sysproxy}; use crate::wrap_err;
use serde_yaml::Mapping;
use network_interface::NetworkInterface; use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use sysproxy::{Autoproxy, Sysproxy};
/// get the system proxy /// get the system proxy
#[tauri::command] #[tauri::command]
@ -46,8 +46,7 @@ pub fn get_network_interfaces() -> Vec<String> {
/// 获取网络接口详细信息 /// 获取网络接口详细信息
#[tauri::command] #[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> { pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::NetworkInterface; use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces(); let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?; let interfaces = wrap_err!(NetworkInterface::show())?;

View File

@ -1,11 +1,11 @@
use super::CmdResult;
use crate::{ use crate::{
config::*, config::*,
core::*, core::*,
feat, feat, log_err, ret_err,
utils::{dirs, help}, utils::{dirs, help},
log_err, ret_err, wrap_err, wrap_err,
}; };
use super::CmdResult;
/// 获取配置文件列表 /// 获取配置文件列表
#[tauri::command] #[tauri::command]
@ -31,7 +31,7 @@ pub async fn enhance_profiles() -> CmdResult {
} }
Err(e) => { Err(e) => {
println!("[enhance_profiles] 更新过程发生错误: {}", e); println!("[enhance_profiles] 更新过程发生错误: {}", e);
handle::Handle::notice_message("config_validate::process_terminated", &e.to_string()); handle::Handle::notice_message("config_validate::process_terminated", e.to_string());
Ok(()) Ok(())
} }
} }
@ -76,9 +76,7 @@ pub async fn delete_profile(index: String) -> CmdResult {
/// 修改profiles的配置 /// 修改profiles的配置
#[tauri::command] #[tauri::command]
pub async fn patch_profiles_config( pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件"); println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复 // 保存当前配置,以便在验证失败时恢复
@ -124,7 +122,7 @@ pub async fn patch_profiles_config(
Err(e) => { Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e); println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard(); Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", &e.to_string()); handle::Handle::notice_message("config_validate::boot_error", e.to_string());
Ok(false) Ok(false)
} }
} }
@ -134,9 +132,12 @@ pub async fn patch_profiles_config(
#[tauri::command] #[tauri::command]
pub async fn patch_profiles_config_by_profile_index( pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle, _app_handle: tauri::AppHandle,
profile_index: String profile_index: String,
) -> CmdResult<bool> { ) -> CmdResult<bool> {
let profiles = IProfiles{current: Some(profile_index), items: None}; let profiles = IProfiles {
current: Some(profile_index),
items: None,
};
patch_profiles_config(profiles).await patch_profiles_config(profiles).await
} }

View File

@ -1,11 +1,8 @@
use crate::{
config::*,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, wrap_err};
use anyhow::Context; use anyhow::Context;
use std::collections::HashMap;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap;
/// 获取运行时配置 /// 获取运行时配置
#[tauri::command] #[tauri::command]

View File

@ -1,10 +1,5 @@
use crate::{
config::*,
core::*,
utils::dirs,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, core::*, utils::dirs, wrap_err};
use std::fs; use std::fs;
/// 保存profiles的配置 /// 保存profiles的配置
@ -20,7 +15,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
let profiles_guard = profiles.latest(); let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?; let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件 // 确定是否为merge类型文件
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge"); let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
let content = wrap_err!(item.read_file())?; let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?; let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?; let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
@ -31,12 +26,18 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?; wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string(); let file_path_str = file_path.to_string_lossy().to_string();
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file); println!(
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str, is_merge_file
);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证 // 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file { if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证"); println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await { match CoreManager::global()
.validate_config_file(&file_path_str, Some(true))
.await
{
Ok((true, _)) => { Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过"); println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置 // 成功后尝试更新整体配置
@ -65,7 +66,10 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
} }
// 非merge文件使用完整验证流程 // 非merge文件使用完整验证流程
match CoreManager::global().validate_config_file(&file_path_str, None).await { match CoreManager::global()
.validate_config_file(&file_path_str, None)
.await
{
Ok((true, _)) => { Ok((true, _)) => {
println!("[cmd配置save] 验证成功"); println!("[cmd配置save] 验证成功");
Ok(()) Ok(())
@ -76,14 +80,15 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
wrap_err!(fs::write(&file_path, original_content))?; wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型 // 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js") || let is_script_error = file_path_str.ends_with(".js")
error_msg.contains("Script syntax error") || || error_msg.contains("Script syntax error")
error_msg.contains("Script must contain a main function") || || error_msg.contains("Script must contain a main function")
error_msg.contains("Failed to read script file"); || error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error") || if error_msg.contains("YAML syntax error")
error_msg.contains("Failed to read file:") || || error_msg.contains("Failed to read file:")
(!file_path_str.ends_with(".js") && !is_script_error) { || (!file_path_str.ends_with(".js") && !is_script_error)
{
// 普通YAML错误使用YAML通知处理 // 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知"); println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone()); let result = (false, error_msg.clone());

View File

@ -1,8 +1,10 @@
use super::CmdResult; use super::CmdResult;
use crate::core::handle; use crate::{
use crate::module::sysinfo::PlatformSpecification; core::{self, handle, service, CoreManager},
module::sysinfo::PlatformSpecification,
wrap_err,
};
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command] #[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> { pub async fn export_diagnostic_info() -> CmdResult<()> {
@ -11,8 +13,7 @@ pub async fn export_diagnostic_info() -> CmdResult<()> {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard(); let cliboard = app_handle.clipboard();
if cliboard.write_text(info).is_err() {
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard"); log::error!(target: "app", "Failed to write to clipboard");
} }
Ok(()) Ok(())

View File

@ -4,8 +4,7 @@ use super::CmdResult;
#[cfg(windows)] #[cfg(windows)]
mod platform { mod platform {
use super::CmdResult; use super::CmdResult;
use crate::core::win_uwp; use crate::{core::win_uwp, wrap_err};
use crate::wrap_err;
pub async fn invoke_uwp_tool() -> CmdResult { pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await) wrap_err!(win_uwp::invoke_uwptools().await)

View File

@ -1,5 +1,5 @@
use crate::core::*;
use super::CmdResult; use super::CmdResult;
use crate::core::*;
/// 发送脚本验证通知消息 /// 发送脚本验证通知消息
#[tauri::command] #[tauri::command]
@ -38,11 +38,14 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> { pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path); log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global().validate_config_file(&file_path, None).await { match CoreManager::global()
.validate_config_file(&file_path, None)
.await
{
Ok(result) => { Ok(result) => {
handle_script_validation_notice(&result, "脚本文件"); handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值 Ok(result.0) // 返回验证结果布尔值
}, }
Err(e) => { Err(e) => {
let error_msg = e.to_string(); let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg); log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);

View File

@ -1,9 +1,5 @@
use crate::{
config::*,
feat,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, feat, wrap_err};
/// 获取Verge配置 /// 获取Verge配置
#[tauri::command] #[tauri::command]

View File

@ -1,10 +1,5 @@
use crate::{
core,
config::*,
feat,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, core, feat, wrap_err};
use reqwest_dav::list_cmd::ListFile; use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置 /// 保存 WebDAV 配置

View File

@ -1,9 +1,9 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge}; use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{ use crate::{
config::PrfItem, config::PrfItem,
core::{handle, CoreManager},
enhance, enhance,
utils::{dirs, help}, utils::{dirs, help},
core::{handle, CoreManager},
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@ -73,14 +73,17 @@ impl Config {
// 生成运行时配置文件并验证 // 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run); let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if let Ok(_) = config_result { let validation_result = if config_result.is_ok() {
// 验证配置文件 // 验证配置文件
println!("[首次启动] 开始验证配置"); println!("[首次启动] 开始验证配置");
match CoreManager::global().validate_config().await { match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => { Ok((is_valid, error_msg)) => {
if !is_valid { if !is_valid {
println!("[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg); println!(
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
error_msg
);
CoreManager::global() CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg) .use_default_config("config_validate::boot_error", &error_msg)
.await?; .await?;
@ -101,10 +104,7 @@ impl Config {
} else { } else {
println!("[首次启动] 生成配置文件失败,使用默认配置"); println!("[首次启动] 生成配置文件失败,使用默认配置");
CoreManager::global() CoreManager::global()
.use_default_config( .use_default_config("config_validate::error", "")
"config_validate::error",
"",
)
.await?; .await?;
Some(("config_validate::error", String::new())) Some(("config_validate::error", String::new()))
}; };

View File

@ -8,14 +8,9 @@ mod profiles;
mod runtime; mod runtime;
mod verge; mod verge;
pub use self::clash::*; pub use self::{
pub use self::config::*; clash::*, config::*, draft::*, encrypt::*, prfitem::*, profiles::*, runtime::*, verge::*,
pub use self::draft::*; };
pub use self::encrypt::*;
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) { 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;"; return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";

View File

@ -234,10 +234,10 @@ impl PrfItem {
option: Option<PrfOption>, option: Option<PrfOption>,
) -> Result<PrfItem> { ) -> Result<PrfItem> {
let opt_ref = option.as_ref(); let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false)); let with_proxy = opt_ref.is_some_and(|o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false)); let self_proxy = opt_ref.is_some_and(|o| o.self_proxy.unwrap_or(false));
let accept_invalid_certs = let accept_invalid_certs =
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false)); opt_ref.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone()); let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval); let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone()); let mut merge = opt_ref.and_then(|o| o.merge.clone());

View File

@ -472,15 +472,17 @@ impl IProfiles {
/// 获取所有的profiles(uid名称) /// 获取所有的profiles(uid名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> { pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
match self.items.as_ref() { self.items.as_ref().map(|items| {
Some(items) => Some(items.iter().filter_map(|e| { items
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) { .iter()
Some((uid, name)) .filter_map(|e| {
} else { if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
None Some((uid, name))
} } else {
}).collect()), None
None => None, }
} })
.collect()
})
} }
} }

View File

@ -1,7 +1,7 @@
use crate::config::DEFAULT_PAC; use crate::{
use crate::config::{deserialize_encrypted, serialize_encrypted}; config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
use crate::utils::i18n; utils::{dirs, help, i18n},
use crate::utils::{dirs, help}; };
use anyhow::Result; use anyhow::Result;
use log::LevelFilter; use log::LevelFilter;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};

View File

@ -1,16 +1,17 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use anyhow::Error; use anyhow::Error;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile}; use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::collections::HashMap; use std::{
use std::env::{consts::OS, temp_dir}; collections::HashMap,
use std::fs; env::{consts::OS, temp_dir},
use std::io::Write; fs,
use std::path::PathBuf; io::Write,
use std::sync::Arc; path::PathBuf,
use std::time::Duration; sync::Arc,
time::Duration,
};
use tokio::time::timeout; use tokio::time::timeout;
use zip::write::SimpleFileOptions; use zip::write::SimpleFileOptions;

View File

@ -1,16 +1,17 @@
use crate::config::*;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use crate::core::tray::Tray; use crate::core::tray::Tray;
use crate::core::{handle, service}; use crate::{
use crate::log_err; config::*,
use crate::module::mihomo::MihomoManager; core::{handle, service},
use crate::utils::{dirs, help}; log_err,
module::mihomo::MihomoManager,
utils::{dirs, help},
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::{path::PathBuf, sync::Arc, time::Duration}; use std::{path::PathBuf, sync::Arc, time::Duration};
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex; use tokio::{sync::Mutex, time::sleep};
use tokio::time::sleep;
#[derive(Debug)] #[derive(Debug)]
pub struct CoreManager { pub struct CoreManager {

View File

@ -1,13 +1,10 @@
use crate::core::handle; use crate::{config::Config, core::handle, feat, log_err, utils::resolve};
use crate::{config::Config, feat, log_err};
use crate::utils::resolve;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tauri::Manager; use tauri::{async_runtime, Manager};
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState}; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
use tauri::async_runtime;
pub struct Hotkey { pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置 current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@ -26,7 +23,10 @@ impl Hotkey {
let verge = Config::verge(); let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true); let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
println!("Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey); println!(
"Initializing hotkeys, global hotkey enabled: {}",
enable_global_hotkey
);
log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey); log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
// 如果全局热键被禁用,则不注册热键 // 如果全局热键被禁用,则不注册热键
@ -85,11 +85,17 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut(); let manager = app_handle.global_shortcut();
println!("Attempting to register hotkey: {} for function: {}", hotkey, func); println!(
"Attempting to register hotkey: {} for function: {}",
hotkey, func
);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func); log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
if manager.is_registered(hotkey) { if manager.is_registered(hotkey) {
println!("Hotkey {} was already registered, unregistering first", hotkey); println!(
"Hotkey {} was already registered, unregistering first",
hotkey
);
log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey); log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey);
manager.unregister(hotkey)?; manager.unregister(hotkey)?;
} }
@ -135,7 +141,7 @@ impl Hotkey {
println!("=== Hotkey Dashboard Window Operation End ==="); println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ==="); log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
} }
}, }
"clash_mode_rule" => || feat::change_clash_mode("rule".into()), "clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()), "clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()), "clash_mode_direct" => || feat::change_clash_mode("direct".into()),
@ -172,7 +178,10 @@ impl Hotkey {
// 获取轻量模式状态和全局热键状态 // 获取轻量模式状态和全局热键状态
let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false); let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false);
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
// 在轻量模式下或配置了全局热键时,始终执行热键功能 // 在轻量模式下或配置了全局热键时,始终执行热键功能
if is_lite_mode || is_enable_global_hotkey { if is_lite_mode || is_enable_global_hotkey {

View File

@ -1,10 +1,7 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::{collections::HashMap, env::current_exe, path::PathBuf, process::Command as StdCommand};
use std::path::PathBuf;
use std::{env::current_exe, process::Command as StdCommand};
use tokio::time::Duration; use tokio::time::Duration;
// Windows only // Windows only
@ -158,7 +155,9 @@ pub async fn reinstall_service() -> Result<()> {
// 获取提示文本,如果 i18n 失败则使用硬编码默认值 // 获取提示文本,如果 i18n 失败则使用硬编码默认值
let prompt = crate::utils::i18n::t("Service Administrator Prompt"); let prompt = crate::utils::i18n::t("Service Administrator Prompt");
let prompt = if prompt == "Service Administrator Prompt" { let prompt = if prompt == "Service Administrator Prompt" {
if Config::verge().latest().language.as_deref() == Some("zh") || Config::verge().latest().language.is_none() { if Config::verge().latest().language.as_deref() == Some("zh")
|| Config::verge().latest().language.is_none()
{
"Clash Verge 需要使用管理员权限来重新安装系统服务" "Clash Verge 需要使用管理员权限来重新安装系统服务"
} else { } else {
"Clash Verge needs administrator privileges to reinstall the system service" "Clash Verge needs administrator privileges to reinstall the system service"

View File

@ -1,6 +1,6 @@
use crate::core::handle::Handle;
use crate::{ use crate::{
config::{Config, IVerge}, config::{Config, IVerge},
core::handle::Handle,
log_err, log_err,
}; };
use anyhow::Result; use anyhow::Result;
@ -126,8 +126,7 @@ impl Sysopt {
if !sys_enable { if !sys_enable {
return self.reset_sysproxy().await; return self.reset_sysproxy().await;
} }
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use anyhow::bail; use anyhow::bail;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
@ -185,8 +184,7 @@ impl Sysopt {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use anyhow::bail; use anyhow::bail;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
@ -305,8 +303,7 @@ impl Sysopt {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap(); let app_handle = Handle::global().app_handle().unwrap();

View File

@ -1,12 +1,9 @@
use crate::config::Config; use crate::{config::Config, core::CoreManager, feat};
use crate::feat;
use crate::core::CoreManager;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder}; use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
type TaskID = u64; type TaskID = u64;
@ -195,16 +192,14 @@ impl Timer {
log::info!(target: "app", "Running timer task `{}`", uid); log::info!(target: "app", "Running timer task `{}`", uid);
match feat::update_profile(uid.clone(), None).await { match feat::update_profile(uid.clone(), None).await {
Ok(_) => { Ok(_) => match CoreManager::global().update_config().await {
match CoreManager::global().update_config().await { Ok(_) => {
Ok(_) => { log::info!(target: "app", "Timer task completed successfully for uid: {}", uid);
log::info!(target: "app", "Timer task completed successfully for uid: {}", uid);
}
Err(e) => {
log::error!(target: "app", "Timer task refresh error for uid {}: {}", uid, e);
}
} }
} Err(e) => {
log::error!(target: "app", "Timer task refresh error for uid {}: {}", uid, e);
}
},
Err(e) => { Err(e) => {
log::error!(target: "app", "Timer task update error for uid {}: {}", uid, e); log::error!(target: "app", "Timer task update error for uid {}: {}", uid, e);
} }

View File

@ -134,7 +134,7 @@ impl Tray {
let profile_uid_and_name = Config::profiles() let profile_uid_and_name = Config::profiles()
.data() .data()
.all_profile_uid_and_name() .all_profile_uid_and_name()
.unwrap_or(Vec::new()); .unwrap_or_default();
let tray = app_handle.tray_by_id("main").unwrap(); let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_menu(Some(create_tray_menu( let _ = tray.set_menu(Some(create_tray_menu(
@ -408,8 +408,8 @@ fn create_tray_menu(
.is_current_profile_index(profile_uid.to_string()); .is_current_profile_index(profile_uid.to_string());
CheckMenuItem::with_id( CheckMenuItem::with_id(
app_handle, app_handle,
&format!("profiles_{}", profile_uid), format!("profiles_{}", profile_uid),
t(&profile_name), t(profile_name),
true, true,
is_current_profile, is_current_profile,
None::<&str>, None::<&str>,

View File

@ -1,16 +1,15 @@
use crate::module::mihomo::Rate; use crate::{
use crate::module::mihomo::MihomoManager; module::mihomo::{MihomoManager, Rate},
use crate::utils::help::format_bytes_speed; utils::help::format_bytes_speed,
};
use ab_glyph::FontArc; use ab_glyph::FontArc;
use anyhow::Result; use anyhow::Result;
use futures::Stream; use futures::Stream;
use image::{GenericImageView, Rgba, RgbaImage}; use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut; use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::io::Cursor; use std::{io::Cursor, sync::Arc};
use std::sync::Arc; use tokio_tungstenite::tungstenite::{http, Message};
use tokio_tungstenite::tungstenite::http;
use tokio_tungstenite::tungstenite::Message;
use tungstenite::client::IntoClientRequest; use tungstenite::client::IntoClientRequest;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpeedRate { pub struct SpeedRate {

View File

@ -15,7 +15,7 @@ impl MihomoManager {
proxies: serde_json::Value::Null, proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null, providers_proxies: serde_json::Value::Null,
})), })),
headers: headers, headers,
} }
} }
@ -76,7 +76,7 @@ impl MihomoManager {
client_response.text().await.map(|text| json!(text)) client_response.text().await.map(|text| json!(text))
} }
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
return Ok(response); Ok(response)
} }
pub async fn refresh_proxies(&self) -> Result<&Self, String> { pub async fn refresh_proxies(&self) -> Result<&Self, String> {
@ -129,6 +129,6 @@ impl MihomoManager {
self.mihomo_server, name, test_url, timeout self.mihomo_server, name, test_url, timeout
); );
let response = self.send_request("GET", url, None).await?; let response = self.send_request("GET", url, None).await?;
return Ok(response); Ok(response)
} }
} }

View File

@ -5,17 +5,10 @@ mod script;
pub mod seq; pub mod seq;
mod tun; mod tun;
use self::chain::*; use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
use self::field::*; use crate::{config::Config, utils::tmpl};
use self::merge::*;
use self::script::*;
use self::seq::*;
use self::tun::*;
use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>; type ResultLog = Vec<(String, String)>;

View File

@ -121,16 +121,32 @@ proxy-groups:
let proxies = config.get("proxies").unwrap().as_sequence().unwrap(); let proxies = config.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(proxies.len(), 1); assert_eq!(proxies.len(), 1);
assert_eq!( assert_eq!(
proxies[0].as_mapping().unwrap().get("name").unwrap().as_str().unwrap(), proxies[0]
.as_mapping()
.unwrap()
.get("name")
.unwrap()
.as_str()
.unwrap(),
"proxy2" "proxy2"
); );
// Check if proxy1 is removed from all groups // Check if proxy1 is removed from all groups
let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap(); let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap();
let group1_proxies = groups[0].as_mapping().unwrap() let group1_proxies = groups[0]
.get("proxies").unwrap().as_sequence().unwrap(); .as_mapping()
let group2_proxies = groups[1].as_mapping().unwrap() .unwrap()
.get("proxies").unwrap().as_sequence().unwrap(); .get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group2_proxies = groups[1]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
assert_eq!(group1_proxies.len(), 1); assert_eq!(group1_proxies.len(), 1);
assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2"); assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2");

View File

@ -40,20 +40,20 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 检查现有的 enhanced-mode 设置 // 检查现有的 enhanced-mode 设置
let current_mode = dns_val let current_mode = dns_val
.get(&Value::from("enhanced-mode")) .get(Value::from("enhanced-mode"))
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.unwrap_or("fake-ip"); .unwrap_or("fake-ip");
// 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置 // 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置
if current_mode == "fake-ip" || !dns_val.contains_key(&Value::from("enhanced-mode")) { if current_mode == "fake-ip" || !dns_val.contains_key(Value::from("enhanced-mode")) {
revise!(dns_val, "enable", true); revise!(dns_val, "enable", true);
revise!(dns_val, "ipv6", ipv6_val); revise!(dns_val, "ipv6", ipv6_val);
if !dns_val.contains_key(&Value::from("enhanced-mode")) { if !dns_val.contains_key(Value::from("enhanced-mode")) {
revise!(dns_val, "enhanced-mode", "fake-ip"); revise!(dns_val, "enhanced-mode", "fake-ip");
} }
if !dns_val.contains_key(&Value::from("fake-ip-range")) { if !dns_val.contains_key(Value::from("fake-ip-range")) {
revise!(dns_val, "fake-ip-range", "198.18.0.1/16"); revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
} }

View File

@ -1,7 +1,9 @@
use crate::config::{Config, IVerge}; use crate::{
use crate::core::backup; config::{Config, IVerge},
use crate::log_err; core::backup,
use crate::utils::dirs::app_home_dir; log_err,
utils::dirs::app_home_dir,
};
use anyhow::Result; use anyhow::Result;
use reqwest_dav::list_cmd::ListFile; use reqwest_dav::list_cmd::ListFile;
use std::fs; use std::fs;

View File

@ -1,8 +1,10 @@
use crate::config::Config; use crate::{
use crate::core::{handle, tray, CoreManager}; config::Config,
use crate::log_err; core::{handle, tray, CoreManager},
use crate::module::mihomo::MihomoManager; log_err,
use crate::utils::resolve; module::mihomo::MihomoManager,
utils::resolve,
};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use tauri::Manager; use tauri::Manager;

View File

@ -1,7 +1,9 @@
use crate::config::{Config, IVerge}; use crate::{
use crate::core::{handle, hotkey, sysopt, tray, CoreManager}; config::{Config, IVerge},
use crate::log_err; core::{handle, hotkey, sysopt, tray, CoreManager},
use crate::utils::resolve; log_err,
utils::resolve,
};
use anyhow::Result; use anyhow::Result;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use tauri::Manager; use tauri::Manager;

View File

@ -1,8 +1,8 @@
use crate::cmd; use crate::{
use crate::config::{Config, PrfItem, PrfOption}; cmd,
use crate::core::handle; config::{Config, PrfItem, PrfOption},
use crate::core::CoreManager; core::{handle, CoreManager, *},
use crate::core::*; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
/// Toggle proxy profile /// Toggle proxy profile
@ -29,7 +29,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
let profiles = Config::profiles(); let profiles = Config::profiles();
let profiles = profiles.latest(); let profiles = profiles.latest();
let item = profiles.get_item(&uid)?; let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote"); let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
if !is_remote { if !is_remote {
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid); println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);

View File

@ -1,6 +1,7 @@
use crate::config::Config; use crate::{
use crate::config::IVerge; config::{Config, IVerge},
use crate::core::handle; core::handle,
};
use std::env; use std::env;
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
@ -72,10 +73,16 @@ pub fn copy_clash_env() {
}; };
let export_text = match env_type.as_str() { let export_text = match env_type.as_str() {
"bash" => format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"), "bash" => format!(
"export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"
),
"cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"), "cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"),
"powershell" => format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\""), "powershell" => {
"nushell" => format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}"), format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"")
}
"nushell" => {
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}")
}
"fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"), "fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"),
_ => { _ => {
log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"); log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}");
@ -83,7 +90,7 @@ pub fn copy_clash_env() {
} }
}; };
if let Err(_) = cliboard.write_text(export_text) { if cliboard.write_text(export_text).is_err() {
log::error!(target: "app", "Failed to write to clipboard"); log::error!(target: "app", "Failed to write to clipboard");
} }
} }

View File

@ -1,9 +1,9 @@
use crate::config::Config; use crate::{
use crate::core::handle; config::Config,
use crate::core::{sysopt, CoreManager}; core::{handle, sysopt, CoreManager},
use crate::module::mihomo::MihomoManager; module::mihomo::MihomoManager,
use crate::utils::resolve; utils::resolve,
use futures; };
use tauri::Manager; use tauri::Manager;
use tauri_plugin_window_state::{AppHandleExt, StateFlags}; use tauri_plugin_window_state::{AppHandleExt, StateFlags};

View File

@ -3,17 +3,19 @@ mod config;
mod core; mod core;
mod enhance; mod enhance;
mod feat; mod feat;
mod utils;
mod module; mod module;
use crate::core::hotkey; mod utils;
use crate::utils::{resolve, resolve::resolve_scheme, server}; use crate::{
core::hotkey,
utils::{resolve, resolve::resolve_scheme, server},
};
use config::Config; use config::Config;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use std::sync::{Mutex, Once}; use std::sync::{Mutex, Once};
use tauri::AppHandle; use tauri::AppHandle;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use tauri::Manager; use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
/// A global singleton handle to the application. /// A global singleton handle to the application.
pub struct AppHandleManager { pub struct AppHandleManager {
@ -77,6 +79,7 @@ impl AppHandleManager {
} }
} }
#[allow(clippy::panic)]
pub fn run() { pub fn run() {
// 单例检测 // 单例检测
let app_exists: bool = tauri::async_runtime::block_on(async move { let app_exists: bool = tauri::async_runtime::block_on(async move {
@ -219,12 +222,18 @@ pub fn run() {
AppHandleManager::global().init(app_handle.clone()); AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let main_window = AppHandleManager::global().get_handle().get_webview_window("main").unwrap(); let main_window = AppHandleManager::global()
.get_handle()
.get_webview_window("main")
.unwrap();
let _ = main_window.set_title("Clash Verge"); let _ = main_window.set_title("Clash Verge");
} }
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
tauri::RunEvent::Reopen { has_visible_windows, .. } => { tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
if !has_visible_windows { if !has_visible_windows {
AppHandleManager::global().set_activation_policy_regular(); AppHandleManager::global().set_activation_policy_regular();
} }
@ -260,7 +269,10 @@ pub fn run() {
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit")); log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
}; };
{ {
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey { if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().init()) log_err!(hotkey::Hotkey::global().init())
} }
@ -276,7 +288,10 @@ pub fn run() {
log_err!(hotkey::Hotkey::global().unregister("Control+Q")); log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
}; };
{ {
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey { if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().reset()) log_err!(hotkey::Hotkey::global().reset())
} }

View File

@ -65,6 +65,6 @@ impl MihomoManager {
.unwrap() .unwrap()
.to_string(); .to_string();
let token = http::header::HeaderValue::from_str(&auth).unwrap(); let token = http::header::HeaderValue::from_str(&auth).unwrap();
return (ws_url, token); (ws_url, token)
} }
} }

View File

@ -1,2 +1,2 @@
pub mod sysinfo;
pub mod mihomo; pub mod mihomo;
pub mod sysinfo;

View File

@ -43,7 +43,6 @@ impl PlatformSpecification {
}) })
}); });
Self { Self {
system_name, system_name,
system_version, system_version,

View File

@ -1,8 +1,7 @@
use crate::core::handle; use crate::core::handle;
use anyhow::Result; use anyhow::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::fs; use std::{fs, path::PathBuf};
use std::path::PathBuf;
use tauri::Manager; use tauri::Manager;
#[cfg(not(feature = "verge-dev"))] #[cfg(not(feature = "verge-dev"))]

View File

@ -1,5 +1,4 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde_json::Value; use serde_json::Value;
use std::{collections::HashMap, fs, path::PathBuf}; use std::{collections::HashMap, fs, path::PathBuf};

View File

@ -1,16 +1,21 @@
use crate::config::*; use crate::{
use crate::core::handle; config::*,
use crate::utils::{dirs, help}; core::handle,
utils::{dirs, help},
};
use anyhow::Result; use anyhow::Result;
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use log::LevelFilter; use log::LevelFilter;
use log4rs::append::console::ConsoleAppender; use log4rs::{
use log4rs::append::file::FileAppender; append::{console::ConsoleAppender, file::FileAppender},
use log4rs::config::{Appender, Logger, Root}; config::{Appender, Logger, Root},
use log4rs::encode::pattern::PatternEncoder; encode::pattern::PatternEncoder,
use std::fs::{self, DirEntry}; };
use std::path::PathBuf; use std::{
use std::str::FromStr; fs::{self, DirEntry},
path::PathBuf,
str::FromStr,
};
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
/// initialize this instance's log file /// initialize this instance's log file
@ -142,58 +147,91 @@ fn init_dns_config() -> Result<()> {
("enable".into(), Value::Bool(true)), ("enable".into(), Value::Bool(true)),
("listen".into(), Value::String(":53".into())), ("listen".into(), Value::String(":53".into())),
("enhanced-mode".into(), Value::String("fake-ip".into())), ("enhanced-mode".into(), Value::String("fake-ip".into())),
("fake-ip-range".into(), Value::String("198.18.0.1/16".into())), (
("fake-ip-filter-mode".into(), Value::String("blacklist".into())), "fake-ip-range".into(),
Value::String("198.18.0.1/16".into()),
),
(
"fake-ip-filter-mode".into(),
Value::String("blacklist".into()),
),
("prefer-h3".into(), Value::Bool(false)), ("prefer-h3".into(), Value::Bool(false)),
("respect-rules".into(), Value::Bool(false)), ("respect-rules".into(), Value::Bool(false)),
("use-hosts".into(), Value::Bool(false)), ("use-hosts".into(), Value::Bool(false)),
("use-system-hosts".into(), Value::Bool(false)), ("use-system-hosts".into(), Value::Bool(false)),
("fake-ip-filter".into(), Value::Sequence(vec![ (
Value::String("*.lan".into()), "fake-ip-filter".into(),
Value::String("*.local".into()), Value::Sequence(vec![
Value::String("*.arpa".into()), Value::String("*.lan".into()),
Value::String("time.*.com".into()), Value::String("*.local".into()),
Value::String("ntp.*.com".into()), Value::String("*.arpa".into()),
Value::String("time.*.com".into()), Value::String("time.*.com".into()),
Value::String("+.market.xiaomi.com".into()), Value::String("ntp.*.com".into()),
Value::String("localhost.ptlogin2.qq.com".into()), Value::String("time.*.com".into()),
Value::String("*.msftncsi.com".into()), Value::String("+.market.xiaomi.com".into()),
Value::String("www.msftconnecttest.com".into()), Value::String("localhost.ptlogin2.qq.com".into()),
])), Value::String("*.msftncsi.com".into()),
("default-nameserver".into(), Value::Sequence(vec![ Value::String("www.msftconnecttest.com".into()),
Value::String("223.6.6.6".into()), ]),
Value::String("8.8.8.8".into()), ),
])), (
("nameserver".into(), Value::Sequence(vec![ "default-nameserver".into(),
Value::String("8.8.8.8".into()), Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()), Value::String("223.6.6.6".into()),
Value::String("https://dns.alidns.com/dns-query".into()), Value::String("8.8.8.8".into()),
])), ]),
("fallback".into(), Value::Sequence(vec![ ),
Value::String("https://dns.alidns.com/dns-query".into()), (
Value::String("https://dns.google/dns-query".into()), "nameserver".into(),
Value::String("https://cloudflare-dns.com/dns-query".into()), Value::Sequence(vec![
])), Value::String("8.8.8.8".into()),
("nameserver-policy".into(), Value::Mapping(serde_yaml::Mapping::new())), Value::String("https://doh.pub/dns-query".into()),
("proxy-server-nameserver".into(), Value::Sequence(vec![ Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("https://doh.pub/dns-query".into()), ]),
Value::String("https://dns.alidns.com/dns-query".into()), ),
])), (
"fallback".into(),
Value::Sequence(vec![
Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("https://dns.google/dns-query".into()),
Value::String("https://cloudflare-dns.com/dns-query".into()),
]),
),
(
"nameserver-policy".into(),
Value::Mapping(serde_yaml::Mapping::new()),
),
(
"proxy-server-nameserver".into(),
Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
]),
),
("direct-nameserver".into(), Value::Sequence(vec![])), ("direct-nameserver".into(), Value::Sequence(vec![])),
("direct-nameserver-follow-policy".into(), Value::Bool(false)), ("direct-nameserver-follow-policy".into(), Value::Bool(false)),
("fallback-filter".into(), Value::Mapping(serde_yaml::Mapping::from_iter([ (
("geoip".into(), Value::Bool(true)), "fallback-filter".into(),
("geoip-code".into(), Value::String("CN".into())), Value::Mapping(serde_yaml::Mapping::from_iter([
("ipcidr".into(), Value::Sequence(vec![ ("geoip".into(), Value::Bool(true)),
Value::String("240.0.0.0/4".into()), ("geoip-code".into(), Value::String("CN".into())),
Value::String("0.0.0.0/32".into()), (
"ipcidr".into(),
Value::Sequence(vec![
Value::String("240.0.0.0/4".into()),
Value::String("0.0.0.0/32".into()),
]),
),
(
"domain".into(),
Value::Sequence(vec![
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
]),
),
])), ])),
("domain".into(), Value::Sequence(vec![ ),
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
])),
]))),
]); ]);
// 检查DNS配置文件是否存在 // 检查DNS配置文件是否存在
@ -202,7 +240,11 @@ fn init_dns_config() -> Result<()> {
if !dns_path.exists() { if !dns_path.exists() {
log::info!(target: "app", "Creating default DNS config file"); log::info!(target: "app", "Creating default DNS config file");
help::save_yaml(&dns_path, &default_dns_config, Some("# Clash Verge DNS Config"))?; help::save_yaml(
&dns_path,
&default_dns_config,
Some("# Clash Verge DNS Config"),
)?;
} }
Ok(()) Ok(())
@ -323,8 +365,7 @@ pub fn init_resources() -> Result<()> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> { pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe; use tauri::utils::platform::current_exe;
use winreg::enums::*; use winreg::{enums::*, RegKey};
use winreg::RegKey;
let app_exe = current_exe()?; let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?; let app_exe = dunce::canonicalize(app_exe)?;

View File

@ -1,8 +1,8 @@
pub mod dirs; pub mod dirs;
pub mod error; pub mod error;
pub mod help; pub mod help;
pub mod i18n;
pub mod init; pub mod init;
pub mod resolve; pub mod resolve;
pub mod server; pub mod server;
pub mod tmpl; pub mod tmpl;
pub mod i18n;

View File

@ -1,9 +1,12 @@
use crate::config::IVerge;
use crate::utils::error;
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
use crate::{log_err, wrap_err};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use crate::AppHandleManager; use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem},
core::*,
log_err,
utils::{error, init, server},
wrap_err,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
@ -315,8 +318,7 @@ fn resolve_random_port_config() -> Result<()> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub async fn set_public_dns(dns_server: String) { pub async fn set_public_dns(dns_server: String) {
use crate::core::handle; use crate::{core::handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
@ -352,8 +354,7 @@ pub async fn set_public_dns(dns_server: String) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub async fn restore_public_dns() { pub async fn restore_public_dns() {
use crate::core::handle; use crate::{core::handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to unset system dns"); log::info!(target: "app", "try to unset system dns");

View File

@ -1,8 +1,10 @@
extern crate warp; extern crate warp;
use super::resolve; use super::resolve;
use crate::config::{Config, IVerge, DEFAULT_PAC}; use crate::{
use crate::log_err; config::{Config, IVerge, DEFAULT_PAC},
log_err,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use port_scanner::local_port_available; use port_scanner::local_port_available;
use std::convert::Infallible; use std::convert::Infallible;

View File

@ -39,7 +39,7 @@ export default defineConfig({
}), }),
], ],
build: { build: {
outDir: "../dist", outDir: "dist",
emptyOutDir: true, emptyOutDir: true,
}, },
resolve: { resolve: {