diff --git a/Changelog.md b/Changelog.md
index 16ce0b9c1..0061878c1 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,11 +3,9 @@
🐞 修复问题
-- 回退 2.5.4 引入的 DNS 覆写保护,修复重启后开关自动关闭
- 优化文件错误日志,显示具体文件路径和失败原因
- 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失
- 修复首次以管理员身份启动后,系统服务因数据目录所有权不符而拒绝启动内核的问题
-- 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失
@@ -16,5 +14,6 @@
- 优化系统服务版本检查:版本与当前应用要求不一致时,提示卸载后重新安装
- 优化预构建资源,补充 ASN 数据库文件
+- 扩展配置或脚本写入由应用设置接管的字段时,弹出提示并在扩展日志中记录该值已被丢弃
diff --git a/src-tauri/src/cmd/runtime.rs b/src-tauri/src/cmd/runtime.rs
index d96168fb7..13e0e7c5f 100644
--- a/src-tauri/src/cmd/runtime.rs
+++ b/src-tauri/src/cmd/runtime.rs
@@ -32,6 +32,11 @@ pub async fn get_runtime_logs() -> CmdResult Option {
+ crate::enhance::take_discarded_keys_notice()
+}
+
#[tauri::command]
pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> CmdResult {
let runtime = Config::runtime().await;
diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs
index c62aaf4d8..db2b6eca2 100644
--- a/src-tauri/src/enhance/mod.rs
+++ b/src-tauri/src/enhance/mod.rs
@@ -14,6 +14,7 @@ use self::{
tun::use_tun,
};
use crate::config::dns::{DnsOverrideState, dns_override_source};
+use crate::core::handle::Handle;
use crate::utils::dirs;
use crate::{
config::{Config, IProfiles, IVerge, PrfItem},
@@ -22,6 +23,7 @@ use crate::{
};
use anyhow::{Context as _, Result};
use clash_verge_logging::{Type, logging};
+use parking_lot::Mutex;
use serde_yaml_ng::{Mapping, Value};
use smartstring::alias::String;
use std::collections::{HashMap, HashSet};
@@ -247,16 +249,33 @@ async fn process_global_items(
global_merge: ChainItem,
global_script: ChainItem,
profile_name: &String,
+ authoritative: &AuthoritativeFields,
) -> (Mapping, Vec, HashMap) {
if let ChainType::Merge(merge) = global_merge.data {
exists_keys.extend(use_keys(&merge));
+ let before = authoritative.current(&config);
config = use_merge(&merge, config);
+ let notes: ResultLog = authoritative
+ .overridden(&before, &authoritative.current(&config))
+ .into_iter()
+ .map(discarded_note)
+ .collect();
+ if !notes.is_empty() {
+ result_map.entry(global_merge.uid).or_default().extend(notes);
+ }
}
if let ChainType::Script(script) = global_script.data {
- let (res_config, changed_keys, logs) = use_script(script, config, profile_name.clone()).await;
+ let before = authoritative.current(&config);
+ let (res_config, changed_keys, mut logs) = use_script(script, config, profile_name.clone()).await;
exists_keys.extend(changed_keys);
config = res_config;
+ logs.extend(
+ authoritative
+ .overridden(&before, &authoritative.current(&config))
+ .into_iter()
+ .map(discarded_note),
+ );
result_map.insert(global_script.uid, logs);
}
@@ -329,6 +348,60 @@ impl AuthoritativeFields {
let config = enforce_tun(config, self.tun);
enforce_dns_ipv6(config, self.dns_ipv6)
}
+
+ /// The owned values as `config` holds them now, for diffing one override.
+ fn current(&self, config: &Mapping) -> Self {
+ let tun_keys: Vec = self.tun.keys().cloned().collect();
+ Self::capture(config, &tun_keys, self.dns_ipv6.is_some())
+ }
+
+ /// Owned keys an override moved off the app value; `enforce` discards those writes.
+ fn overridden(&self, before: &Self, after: &Self) -> Vec {
+ let mut keys = Vec::new();
+ for &key in CONTROL_PLANE_KEYS {
+ if before.control_plane.get(key) != after.control_plane.get(key)
+ && after.control_plane.get(key) != self.control_plane.get(key)
+ {
+ keys.push(key.into());
+ }
+ }
+ for key in self.tun.keys() {
+ if before.tun.get(key) != after.tun.get(key) && after.tun.get(key) != self.tun.get(key) {
+ keys.push(format!("tun.{}", key.as_str().unwrap_or_default()).into());
+ }
+ }
+ if self.dns_ipv6.is_some() && before.dns_ipv6 != after.dns_ipv6 && after.dns_ipv6 != self.dns_ipv6 {
+ keys.push("dns.ipv6".into());
+ }
+ keys
+ }
+}
+
+fn discarded_note(key: String) -> (String, String) {
+ (
+ "warn".into(),
+ format!("`{key}` is managed by Settings; the value written here was discarded").into(),
+ )
+}
+
+static PENDING_DISCARDED_KEYS: Mutex