use crate::config::Config; use crate::{ config::{DEFAULT_PAC, deserialize_encrypted, serialize_encrypted}, logging, utils::{dirs, help, i18n, logging::Type}, }; use anyhow::Result; use log::LevelFilter; use serde::{Deserialize, Serialize}; use smartstring::alias::String; /// ### `verge.yaml` schema #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct IVerge { /// app log level /// silent | error | warn | info | debug | trace pub app_log_level: Option, /// app log max size in KB pub app_log_max_size: Option, /// app log max count pub app_log_max_count: Option, // i18n pub language: Option, /// `light` or `dark` or `system` pub theme_mode: Option, /// tray click event pub tray_event: Option, /// copy env type pub env_type: Option, /// start page pub start_page: Option, /// startup script path pub startup_script: Option, /// enable traffic graph default is true pub traffic_graph: Option, /// show memory info (only for Clash Meta) pub enable_memory_usage: Option, /// enable group icon #[serde(skip_serializing_if = "Option::is_none")] pub enable_group_icon: Option, /// common tray icon #[serde(skip_serializing_if = "Option::is_none")] pub common_tray_icon: Option, /// tray icon #[cfg(target_os = "macos")] #[serde(skip_serializing_if = "Option::is_none")] pub tray_icon: Option, /// menu icon #[serde(skip_serializing_if = "Option::is_none")] pub menu_icon: Option, /// menu order #[serde(skip_serializing_if = "Option::is_none")] pub menu_order: Option>, /// sysproxy tray icon pub sysproxy_tray_icon: Option, /// tun tray icon pub tun_tray_icon: Option, /// clash tun mode pub enable_tun_mode: Option, /// can the app auto startup pub enable_auto_launch: Option, /// not show the window on launch pub enable_silent_start: Option, /// set system proxy pub enable_system_proxy: Option, /// enable proxy guard pub enable_proxy_guard: Option, /// enable dns settings - this controls whether dns_config.yaml is applied pub enable_dns_settings: Option, /// always use default bypass pub use_default_bypass: Option, /// set system proxy bypass pub system_proxy_bypass: Option, /// proxy guard duration pub proxy_guard_duration: Option, /// use pac mode pub proxy_auto_config: Option, /// pac script content pub pac_file_content: Option, /// proxy host address pub proxy_host: Option, /// theme setting pub theme_setting: Option, /// web ui list pub web_ui_list: Option>, /// clash core path #[serde(skip_serializing_if = "Option::is_none")] pub clash_core: Option, /// hotkey map /// format: {func},{key} #[serde(skip_serializing_if = "Option::is_none")] pub hotkeys: Option>, /// enable global hotkey pub enable_global_hotkey: Option, /// 首页卡片设置 /// 控制首页各个卡片的显示和隐藏 pub home_cards: Option, /// 切换代理时自动关闭连接 pub auto_close_connection: Option, /// 是否自动检查更新 pub auto_check_update: Option, /// 默认的延迟测试连接 pub default_latency_test: Option, /// 默认的延迟测试超时时间 pub default_latency_timeout: Option, /// 是否自动检测当前节点延迟 pub enable_auto_delay_detection: Option, /// 是否使用内部的脚本支持,默认为真 pub enable_builtin_enhanced: Option, /// proxy 页面布局 列数 pub proxy_layout_column: Option, /// 测试站列表 pub test_list: Option>, /// 日志清理 /// 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天 pub auto_log_clean: Option, /// Enable scheduled automatic backups pub enable_auto_backup_schedule: Option, /// Automatic backup interval in hours pub auto_backup_interval_hours: Option, /// Create backups automatically when critical configs change pub auto_backup_on_change: Option, /// verge 的各种 port 用于覆盖 clash 的各种 port #[cfg(not(target_os = "windows"))] pub verge_redir_port: Option, #[cfg(not(target_os = "windows"))] pub verge_redir_enabled: Option, #[cfg(target_os = "linux")] pub verge_tproxy_port: Option, #[cfg(target_os = "linux")] pub verge_tproxy_enabled: Option, pub verge_mixed_port: Option, pub verge_socks_port: Option, pub verge_socks_enabled: Option, pub verge_port: Option, pub verge_http_enabled: Option, /// WebDAV 配置 (加密存储) #[serde( serialize_with = "serialize_encrypted", deserialize_with = "deserialize_encrypted", skip_serializing_if = "Option::is_none", default )] pub webdav_url: Option, /// WebDAV 用户名 (加密存储) #[serde( serialize_with = "serialize_encrypted", deserialize_with = "deserialize_encrypted", skip_serializing_if = "Option::is_none", default )] pub webdav_username: Option, /// WebDAV 密码 (加密存储) #[serde( serialize_with = "serialize_encrypted", deserialize_with = "deserialize_encrypted", skip_serializing_if = "Option::is_none", default )] pub webdav_password: Option, #[serde(skip)] pub enable_tray_speed: Option, // pub enable_tray_icon: Option, /// show proxy groups directly on tray root menu pub tray_inline_proxy_groups: Option, /// 自动进入轻量模式 pub enable_auto_light_weight_mode: Option, /// 自动进入轻量模式的延迟(分钟) pub auto_light_weight_minutes: Option, /// 启用代理页面自动滚动 pub enable_hover_jump_navigator: Option, /// 代理页面自动滚动延迟(毫秒) pub hover_jump_navigator_delay: Option, /// 启用外部控制器 pub enable_external_controller: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct IVergeTestItem { pub uid: Option, pub name: Option, pub icon: Option, pub url: Option, } #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct IVergeTheme { pub primary_color: Option, pub secondary_color: Option, pub primary_text: Option, pub secondary_text: Option, pub info_color: Option, pub error_color: Option, pub warning_color: Option, pub success_color: Option, pub font_family: Option, pub css_injection: Option, } impl IVerge { /// 有效的clash核心名称 pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"]; /// 验证并修正配置文件中的clash_core值 pub async fn validate_and_fix_config() -> Result<()> { let config_path = dirs::verge_path()?; let mut config = match help::read_yaml::(&config_path).await { Ok(config) => config, Err(_) => Self::template(), }; let mut needs_fix = false; if let Some(ref core) = config.clash_core { let core_str = core.trim(); if core_str.is_empty() || !Self::VALID_CLASH_CORES.contains(&core_str) { logging!( warn, Type::Config, "启动时发现无效的clash_core配置: '{}', 将自动修正为 'verge-mihomo'", core ); config.clash_core = Some("verge-mihomo".into()); needs_fix = true; } } else { logging!( info, Type::Config, "启动时发现未配置clash_core, 将设置为默认值 'verge-mihomo'" ); config.clash_core = Some("verge-mihomo".into()); needs_fix = true; } // 修正后保存配置 if needs_fix { logging!(info, Type::Config, "正在保存修正后的配置文件..."); help::save_yaml(&config_path, &config, Some("# Clash Verge Config")).await?; logging!(info, Type::Config, "配置文件修正完成,需要重新加载配置"); Self::reload_config_after_fix(config).await?; } else { logging!( info, Type::Config, "clash_core配置验证通过: {:?}", config.clash_core ); } Ok(()) } /// 配置修正后重新加载配置 async fn reload_config_after_fix(updated_config: Self) -> Result<()> { logging!( info, Type::Config, "内存配置已强制更新,新的clash_core: {:?}", &updated_config.clash_core ); let config_draft = Config::verge().await; config_draft.edit_draft(|d| { *d = updated_config; }); config_draft.apply(); Ok(()) } pub fn get_valid_clash_core(&self) -> String { self.clash_core .clone() .unwrap_or_else(|| "verge-mihomo".into()) } fn get_system_language() -> String { let sys_lang = sys_locale::get_locale() .unwrap_or_else(|| "en".into()) .to_lowercase(); let lang_code = sys_lang.split(['_', '-']).next().unwrap_or("en"); let supported_languages = i18n::get_supported_languages(); if supported_languages.contains(&lang_code.into()) { lang_code.into() } else { String::from("en") } } pub async fn new() -> Self { match dirs::verge_path() { Ok(path) => match help::read_yaml::(&path).await { Ok(mut config) => { // compatibility if let Some(start_page) = config.start_page.clone() && start_page == "/home" { config.start_page = Some(String::from("/")); } config } Err(err) => { logging!(error, Type::Config, "{err}"); Self::template() } }, Err(err) => { logging!(error, Type::Config, "{err}"); Self::template() } } } pub fn template() -> Self { Self { app_log_max_size: Some(128), app_log_max_count: Some(8), clash_core: Some("verge-mihomo".into()), language: Some(Self::get_system_language()), theme_mode: Some("system".into()), #[cfg(not(target_os = "windows"))] env_type: Some("bash".into()), #[cfg(target_os = "windows")] env_type: Some("powershell".into()), start_page: Some("/".into()), traffic_graph: Some(true), enable_memory_usage: Some(true), enable_group_icon: Some(true), #[cfg(target_os = "macos")] tray_icon: Some("monochrome".into()), menu_icon: Some("monochrome".into()), common_tray_icon: Some(false), sysproxy_tray_icon: Some(false), tun_tray_icon: Some(false), enable_auto_launch: Some(false), enable_silent_start: Some(false), enable_hover_jump_navigator: Some(true), hover_jump_navigator_delay: Some(280), enable_system_proxy: Some(false), proxy_auto_config: Some(false), pac_file_content: Some(DEFAULT_PAC.into()), proxy_host: Some("127.0.0.1".into()), #[cfg(not(target_os = "windows"))] verge_redir_port: Some(7895), #[cfg(not(target_os = "windows"))] verge_redir_enabled: Some(false), #[cfg(target_os = "linux")] verge_tproxy_port: Some(7896), #[cfg(target_os = "linux")] verge_tproxy_enabled: Some(false), verge_mixed_port: Some(7897), verge_socks_port: Some(7898), verge_socks_enabled: Some(false), verge_port: Some(7899), verge_http_enabled: Some(false), enable_proxy_guard: Some(false), use_default_bypass: Some(true), proxy_guard_duration: Some(30), auto_close_connection: Some(true), auto_check_update: Some(true), enable_builtin_enhanced: Some(true), auto_log_clean: Some(2), // 1: 1天, 2: 7天, 3: 30天, 4: 90天 enable_auto_backup_schedule: Some(false), auto_backup_interval_hours: Some(24), auto_backup_on_change: Some(true), webdav_url: None, webdav_username: None, webdav_password: None, enable_tray_speed: Some(false), // enable_tray_icon: Some(true), tray_inline_proxy_groups: Some(true), enable_global_hotkey: Some(true), enable_auto_light_weight_mode: Some(false), auto_light_weight_minutes: Some(10), enable_dns_settings: Some(false), home_cards: None, enable_external_controller: Some(false), ..Self::default() } } /// Save IVerge App Config pub async fn save_file(&self) -> Result<()> { help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config")).await } /// patch verge config /// only save to file #[allow(clippy::cognitive_complexity)] pub fn patch_config(&mut self, patch: &Self) { macro_rules! patch { ($key: tt) => { if patch.$key.is_some() { self.$key = patch.$key.clone(); } }; } patch!(app_log_level); patch!(app_log_max_size); patch!(app_log_max_count); patch!(language); patch!(theme_mode); patch!(tray_event); patch!(env_type); patch!(start_page); patch!(startup_script); patch!(traffic_graph); patch!(enable_memory_usage); patch!(enable_group_icon); #[cfg(target_os = "macos")] patch!(tray_icon); patch!(menu_icon); patch!(menu_order); patch!(common_tray_icon); patch!(sysproxy_tray_icon); patch!(tun_tray_icon); patch!(enable_tun_mode); patch!(enable_auto_launch); patch!(enable_silent_start); patch!(enable_hover_jump_navigator); patch!(hover_jump_navigator_delay); #[cfg(not(target_os = "windows"))] patch!(verge_redir_port); #[cfg(not(target_os = "windows"))] patch!(verge_redir_enabled); #[cfg(target_os = "linux")] patch!(verge_tproxy_port); #[cfg(target_os = "linux")] patch!(verge_tproxy_enabled); patch!(verge_mixed_port); patch!(verge_socks_port); patch!(verge_socks_enabled); patch!(verge_port); patch!(verge_http_enabled); patch!(enable_system_proxy); patch!(enable_proxy_guard); patch!(use_default_bypass); patch!(system_proxy_bypass); patch!(proxy_guard_duration); patch!(proxy_auto_config); patch!(pac_file_content); patch!(proxy_host); patch!(theme_setting); patch!(web_ui_list); patch!(clash_core); patch!(hotkeys); patch!(enable_global_hotkey); patch!(auto_close_connection); patch!(auto_check_update); patch!(default_latency_test); patch!(default_latency_timeout); patch!(enable_auto_delay_detection); patch!(enable_builtin_enhanced); patch!(proxy_layout_column); patch!(test_list); patch!(auto_log_clean); patch!(enable_auto_backup_schedule); patch!(auto_backup_interval_hours); patch!(auto_backup_on_change); patch!(webdav_url); patch!(webdav_username); patch!(webdav_password); patch!(enable_tray_speed); // patch!(enable_tray_icon); patch!(tray_inline_proxy_groups); patch!(enable_auto_light_weight_mode); patch!(auto_light_weight_minutes); patch!(enable_dns_settings); patch!(home_cards); patch!(enable_external_controller); } pub const fn get_singleton_port() -> u16 { crate::constants::network::ports::SINGLETON_SERVER } /// 获取日志等级 pub fn get_log_level(&self) -> LevelFilter { if let Some(level) = self.app_log_level.as_ref() { match level.to_lowercase().as_str() { "silent" => LevelFilter::Off, "error" => LevelFilter::Error, "warn" => LevelFilter::Warn, "info" => LevelFilter::Info, "debug" => LevelFilter::Debug, "trace" => LevelFilter::Trace, _ => LevelFilter::Info, } } else { LevelFilter::Info } } }