mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-12 21:52:13 +08:00
Save the DNS override switch and session confirmation per profile so automatic protection only disables the affected profile. Preserve the legacy switch as the initial preference and show the profile scope beside the settings gear and in the DNS editor. Refs #7993
514 lines
16 KiB
Rust
514 lines
16 KiB
Rust
use crate::config::Config;
|
|
use crate::{
|
|
config::{DEFAULT_PAC, deserialize_encrypted, serialize_encrypted},
|
|
constants::network,
|
|
utils::{dirs, help},
|
|
};
|
|
use anyhow::Result;
|
|
use clash_verge_logging::{Type, logging};
|
|
use log::LevelFilter;
|
|
use serde::{Deserialize, Serialize};
|
|
use smartstring::alias::String;
|
|
|
|
/// ### `verge.yaml` schema
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
|
pub struct IVerge {
|
|
/// silent | error | warn | info | debug | trace
|
|
pub app_log_level: Option<String>,
|
|
|
|
/// Maximum app log size in KiB.
|
|
pub app_log_max_size: Option<u64>,
|
|
|
|
pub app_log_max_count: Option<usize>,
|
|
|
|
pub language: Option<String>,
|
|
|
|
/// `light` or `dark` or `system`
|
|
pub theme_mode: Option<String>,
|
|
|
|
pub tray_event: Option<String>,
|
|
|
|
pub env_type: Option<String>,
|
|
|
|
pub start_page: Option<String>,
|
|
pub startup_script: Option<String>,
|
|
|
|
pub traffic_graph: Option<bool>,
|
|
|
|
/// show memory info (only for Clash Meta)
|
|
pub enable_memory_usage: Option<bool>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub enable_group_icon: Option<bool>,
|
|
|
|
pub pause_render_traffic_stats_on_blur: Option<bool>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub common_tray_icon: Option<bool>,
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tray_icon: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub menu_icon: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub menu_order: Option<Vec<String>>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub proxy_group_tools_position: Option<String>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub notice_position: Option<String>,
|
|
|
|
pub collapse_navbar: Option<bool>,
|
|
|
|
pub sysproxy_tray_icon: Option<bool>,
|
|
|
|
pub tun_tray_icon: Option<bool>,
|
|
|
|
pub enable_tun_mode: Option<bool>,
|
|
|
|
pub enable_auto_launch: Option<bool>,
|
|
|
|
pub enable_silent_start: Option<bool>,
|
|
|
|
pub enable_system_proxy: Option<bool>,
|
|
|
|
pub enable_proxy_guard: Option<bool>,
|
|
|
|
pub enable_bypass_check: Option<bool>,
|
|
|
|
/// Initial DNS override preference for profiles without a saved setting.
|
|
pub enable_dns_settings: Option<bool>,
|
|
|
|
#[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
|
|
pub profile_dns_settings: std::collections::BTreeMap<String, super::dns::ProfileDnsSettings>,
|
|
|
|
pub use_default_bypass: Option<bool>,
|
|
|
|
pub system_proxy_bypass: Option<String>,
|
|
|
|
pub proxy_guard_duration: Option<u64>,
|
|
|
|
pub proxy_auto_config: Option<bool>,
|
|
|
|
pub pac_file_content: Option<String>,
|
|
|
|
pub proxy_host: Option<String>,
|
|
|
|
pub theme_setting: Option<IVergeTheme>,
|
|
|
|
pub web_ui_list: Option<Vec<String>>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub clash_core: Option<String>,
|
|
|
|
/// format: {func},{key}
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub hotkeys: Option<Vec<String>>,
|
|
|
|
pub enable_global_hotkey: Option<bool>,
|
|
|
|
pub home_cards: Option<serde_json::Value>,
|
|
|
|
pub auto_close_connection: Option<bool>,
|
|
|
|
pub auto_check_update: Option<bool>,
|
|
|
|
pub default_latency_test: Option<String>,
|
|
|
|
pub default_latency_timeout: Option<i16>,
|
|
|
|
pub enable_auto_delay_detection: Option<bool>,
|
|
|
|
/// 自动检测当前节点延迟的间隔(分钟)
|
|
pub auto_delay_detection_interval_minutes: Option<u64>,
|
|
|
|
pub enable_builtin_enhanced: Option<bool>,
|
|
|
|
pub proxy_layout_column: Option<u8>,
|
|
|
|
pub test_list: Option<Vec<IVergeTestItem>>,
|
|
|
|
/// 0: 不清理; 1: 1天;2: 7天; 3: 30天; 4: 90天
|
|
pub auto_log_clean: Option<i32>,
|
|
|
|
pub enable_auto_backup_schedule: Option<bool>,
|
|
|
|
/// Automatic backup interval in hours
|
|
pub auto_backup_interval_hours: Option<u64>,
|
|
|
|
pub auto_backup_on_change: Option<bool>,
|
|
|
|
/// verge 的各种 port 用于覆盖 clash 的各种 port
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub verge_redir_port: Option<u16>,
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
pub verge_redir_enabled: Option<bool>,
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub verge_tproxy_port: Option<u16>,
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub verge_tproxy_enabled: Option<bool>,
|
|
|
|
pub verge_mixed_port: Option<u16>,
|
|
|
|
pub verge_socks_port: Option<u16>,
|
|
|
|
pub verge_socks_enabled: Option<bool>,
|
|
|
|
pub verge_port: Option<u16>,
|
|
|
|
pub verge_http_enabled: Option<bool>,
|
|
|
|
#[serde(
|
|
serialize_with = "serialize_encrypted",
|
|
deserialize_with = "deserialize_encrypted",
|
|
skip_serializing_if = "Option::is_none",
|
|
default
|
|
)]
|
|
pub webdav_url: Option<String>,
|
|
|
|
#[serde(
|
|
serialize_with = "serialize_encrypted",
|
|
deserialize_with = "deserialize_encrypted",
|
|
skip_serializing_if = "Option::is_none",
|
|
default
|
|
)]
|
|
pub webdav_username: Option<String>,
|
|
|
|
#[serde(
|
|
serialize_with = "serialize_encrypted",
|
|
deserialize_with = "deserialize_encrypted",
|
|
skip_serializing_if = "Option::is_none",
|
|
default
|
|
)]
|
|
pub webdav_password: Option<String>,
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub enable_tray_speed: Option<bool>,
|
|
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub tray_proxy_groups_display_mode: Option<String>,
|
|
pub tray_inline_outbound_modes: Option<bool>,
|
|
|
|
pub enable_auto_light_weight_mode: Option<bool>,
|
|
|
|
/// 自动进入轻量模式的延迟(分钟)
|
|
pub auto_light_weight_minutes: Option<u64>,
|
|
|
|
pub enable_hover_jump_navigator: Option<bool>,
|
|
|
|
/// 代理页面自动滚动延迟(毫秒)
|
|
pub hover_jump_navigator_delay: Option<u64>,
|
|
|
|
pub enable_external_controller: Option<bool>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
|
pub struct IVergeTestItem {
|
|
pub uid: Option<String>,
|
|
pub name: Option<String>,
|
|
pub icon: Option<String>,
|
|
pub url: Option<String>,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
|
pub struct IVergeTheme {
|
|
pub primary_color: Option<String>,
|
|
pub secondary_color: Option<String>,
|
|
pub primary_text: Option<String>,
|
|
pub secondary_text: Option<String>,
|
|
|
|
pub info_color: Option<String>,
|
|
pub error_color: Option<String>,
|
|
pub warning_color: Option<String>,
|
|
pub success_color: Option<String>,
|
|
|
|
pub font_family: Option<String>,
|
|
pub css_injection: Option<String>,
|
|
}
|
|
|
|
impl IVerge {
|
|
pub const VALID_CLASH_CORES: &'static [&'static str] = &["verge-mihomo", "verge-mihomo-alpha"];
|
|
|
|
pub async fn validate_and_fix_config() -> Result<()> {
|
|
let config_path = dirs::verge_path()?;
|
|
let mut config = match help::read_yaml::<Self>(&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!(debug, 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!(debug, Type::Config, "clash_core配置验证通过: {:?}", config.clash_core);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn reload_config_after_fix(updated_config: Self) {
|
|
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();
|
|
}
|
|
|
|
pub fn get_valid_clash_core(&self) -> String {
|
|
self.clash_core.clone().unwrap_or_else(|| "verge-mihomo".into())
|
|
}
|
|
|
|
pub async fn new() -> Self {
|
|
match dirs::verge_path() {
|
|
Ok(path) => match help::read_yaml::<Self>(&path).await {
|
|
Ok(mut config) => {
|
|
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, "failed to read verge config: {err:#}");
|
|
Self::template()
|
|
}
|
|
},
|
|
Err(err) => {
|
|
logging!(error, Type::Config, "failed to get verge config path: {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(clash_verge_i18n::system_language().into()),
|
|
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),
|
|
pause_render_traffic_stats_on_blur: Some(true),
|
|
#[cfg(target_os = "macos")]
|
|
tray_icon: Some("monochrome".into()),
|
|
menu_icon: Some("monochrome".into()),
|
|
notice_position: Some("top-right".into()),
|
|
collapse_navbar: Some(false),
|
|
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(network::ports::DEFAULT_MIXED),
|
|
verge_socks_port: Some(7898),
|
|
verge_socks_enabled: Some(false),
|
|
verge_port: Some(7899),
|
|
verge_http_enabled: Some(false),
|
|
enable_proxy_guard: Some(false),
|
|
enable_bypass_check: Some(true),
|
|
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,
|
|
#[cfg(target_os = "macos")]
|
|
enable_tray_speed: Some(false),
|
|
tray_proxy_groups_display_mode: Some("default".into()),
|
|
tray_inline_outbound_modes: Some(false),
|
|
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()
|
|
}
|
|
}
|
|
|
|
pub async fn save_file(&self) -> Result<()> {
|
|
help::save_yaml(&dirs::verge_path()?, &self, Some("# Clash Verge Config")).await
|
|
}
|
|
|
|
#[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);
|
|
patch!(pause_render_traffic_stats_on_blur);
|
|
#[cfg(target_os = "macos")]
|
|
patch!(tray_icon);
|
|
patch!(menu_icon);
|
|
patch!(menu_order);
|
|
patch!(proxy_group_tools_position);
|
|
patch!(notice_position);
|
|
patch!(collapse_navbar);
|
|
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!(enable_bypass_check);
|
|
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!(auto_delay_detection_interval_minutes);
|
|
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);
|
|
#[cfg(target_os = "macos")]
|
|
patch!(enable_tray_speed);
|
|
patch!(tray_proxy_groups_display_mode);
|
|
patch!(tray_inline_outbound_modes);
|
|
patch!(enable_auto_light_weight_mode);
|
|
patch!(auto_light_weight_minutes);
|
|
patch!(enable_dns_settings);
|
|
patch!(home_cards);
|
|
patch!(enable_external_controller);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|