mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-29 01:52:08 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03d8c81169 |
@@ -5,7 +5,7 @@
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:2": {
|
||||
"version": "24.22.1",
|
||||
"pnpmVersion": "12.15.0"
|
||||
"pnpmVersion": "12.16.1"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
"version": "1.99.1",
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
nodejs 24.22.1
|
||||
pnpm 12.15.0
|
||||
pnpm 12.16.1
|
||||
rust 1.99.1
|
||||
|
||||
+1
-1
@@ -147,5 +147,5 @@
|
||||
]
|
||||
},
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@12.15.0"
|
||||
"packageManager": "pnpm@12.16.1"
|
||||
}
|
||||
|
||||
Generated
+61
-61
@@ -7,153 +7,153 @@ importers:
|
||||
configDependencies: {}
|
||||
packageManagerDependencies:
|
||||
pnpm:
|
||||
specifier: 12.15.0
|
||||
version: 12.15.0
|
||||
specifier: 12.16.1
|
||||
version: 12.16.1
|
||||
|
||||
packages:
|
||||
|
||||
'@pnpm/exe.android-arm64@12.15.0':
|
||||
resolution: {integrity: sha512-eVCqmscY70LGvgRzu3mi8IBWBFGix+a6mNZoWmXxESsBdeiKEF9xQSuaeel10yn+zI7TXy2jlI63vFLzETk+aQ==}
|
||||
'@pnpm/exe.android-arm64@12.16.1':
|
||||
resolution: {integrity: sha512-PKf78Ew+i0CBYf0Xc+eMcqQiqyBT+u8XCVg1F5roc98/6wWfdYlWhrUMmEBiKWaJ3JqHxrtt8l1NwZ7qfJt2/Q==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.android-x64@12.15.0':
|
||||
resolution: {integrity: sha512-Gx/WbkFcdUFUg8g8AShezcS9EiQxfYvdAm2hfKLO8sfJmJvKOCt5fzlgHWDzmGhoclibgW+tUGZ/KF4F4wOTCA==}
|
||||
'@pnpm/exe.android-x64@12.16.1':
|
||||
resolution: {integrity: sha512-xdu7gMbB98pdvhwMeA6612Of515FDlRUtfA/1f7nWPZfPLnEdSRBO2u+1N0XBtQ/ennhPH/lrxB1gC6gnR/cdg==}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.15.0':
|
||||
resolution: {integrity: sha512-N0ZykiDWCvqSBJvt71j7cSoBR993FqvYZFtmpCtqvW3pguiI+NAgraLPA5OTwu5mV+/d8hYpVcPktJA3xFDLtA==}
|
||||
'@pnpm/exe.darwin-arm64@12.16.1':
|
||||
resolution: {integrity: sha512-pVbmIIbhixiLvADLu1WA3TaW8FZy/D72R1De27WbbFx8/2mxvnsTyFVm4fO9kjpjMY7RzJd48CiQ96p8fO3ebQ==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.15.0':
|
||||
resolution: {integrity: sha512-7Ntr2HlBUxzwWOg7NwDxCbRW/3vXuqKO+hcXm+w3SBi9rrHY53li1wLqWVT7Gjxd+lVEHM1QJZo6WJEGFU8C6A==}
|
||||
'@pnpm/exe.darwin-x64@12.16.1':
|
||||
resolution: {integrity: sha512-xZvQBFqcZjO7vFKTK3XsJpqY6lnBL9EWSLCzVUPq3kZ94+D96gwJCa76xxGNK8Cj32f4eeM91Qg+yNRR4sRpDw==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@pnpm/exe.freebsd-x64@12.15.0':
|
||||
resolution: {integrity: sha512-btOiyL7fL7W4uQGH0aqFai2TNC3IoKcZ7gNHBVbEFZt2dWrnrD0SufL3iGs3fLlmb4OtuU35uf4Zpr9V9Jh9mA==}
|
||||
'@pnpm/exe.freebsd-x64@12.16.1':
|
||||
resolution: {integrity: sha512-zTtunfi9mBhRXiXEJYfwiJj0tk7MgTSfqDSRh+Az9FZ9THazVIiQiVNS8jpcsftDOqu7LKNffQdjmgwHDThfUg==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.15.0':
|
||||
resolution: {integrity: sha512-pNC38k+O+90FOjbPNSnw0E7apq1PKTmz3fFe1amn+lys/U5kL++uzir8bRiho8ONcyA3d9EU7wdqcY0DKkj+JA==}
|
||||
'@pnpm/exe.linux-arm64-musl@12.16.1':
|
||||
resolution: {integrity: sha512-BJ3mOmcmQgcUo92Ee/WfEOt1pRiv3y2MMAkhEiYHd85UiPpHTfFhv+SpCbALmfExzXk3UlaRtMYwHlkQymc4VA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.15.0':
|
||||
resolution: {integrity: sha512-qML/8aCXslh8td/w4U+HJNqUcZ2+m6EfTbWScIzZRmyPsRFN+xB4LP1U/hymxc1iSJiKtG56HJx2fvUErWFIGw==}
|
||||
'@pnpm/exe.linux-arm64@12.16.1':
|
||||
resolution: {integrity: sha512-JaRd5gNxwdNGBBkrZFt8bknrK4YmHVJBW0JMhUQ0fJ0dEYp6C2GT9ExaCqOO8EwYEyqM+jpKgJIK+8WFaJy3ew==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-ppc64@12.15.0':
|
||||
resolution: {integrity: sha512-tjRDUUl7RCJ6T/6eoNPxOqkwU4Mzi5RtTXmSAxujyz37OTwwsMU7Ps087Zk4gHcUvRSeyF40ZFsew3vtE+nmUA==}
|
||||
'@pnpm/exe.linux-ppc64@12.16.1':
|
||||
resolution: {integrity: sha512-FnSV0Jv40S1cMv7vFNWwvu0X567Lec4L4HCRead3CFcD6zQDNbGw81s8N5bcKPahtPD9ApVuhsbmFCx4rw6RMw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.15.0':
|
||||
resolution: {integrity: sha512-bF1uBWEfbwaeryz6lIiaJ8AWD082otZfvzGX5l+6LZRpRwUEpJHUsJrORaYP5/cNMv5ZR2p6TvvcJcQzwtDzmw==}
|
||||
'@pnpm/exe.linux-riscv64@12.16.1':
|
||||
resolution: {integrity: sha512-C1Sww4FN5IttD4atRRhAKmQtSGWVp04ZAKNxO8pfu2bkRPwOW+7v+2DH1Xs1W+kZYYpO86BYDvUcadefF+AgaQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.15.0':
|
||||
resolution: {integrity: sha512-zKMhIzsexYMToeLmqV5jPsvV/f9vwsJb2kUH2NFYzalbxImeBrlA4kL7GwF8gD4pagV6jYYcJb8RjbZIdjHptg==}
|
||||
'@pnpm/exe.linux-s390x@12.16.1':
|
||||
resolution: {integrity: sha512-kn2fautsTcxIMODoovQVqZyfpDRdNgGkoeVqNdrKqbeBBvrEl+fXLkmZiq5xDht1LMr4sYiCNcsMfNMT6JPFmg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.15.0':
|
||||
resolution: {integrity: sha512-A9J0Jg0Rdx+qQU4qXtwyST5tto9IOu1zi4fDk35nbc6DWahggbcKtFvZPKFwL2qgUXGXOX//hXv6z/sp6emO8A==}
|
||||
'@pnpm/exe.linux-x64-musl@12.16.1':
|
||||
resolution: {integrity: sha512-PM6ccHCOe/O9SsTflTJTYWfwogJZa6EmUvpnwZgiPguJBddh20wDQKgxUPXf48rDsPm9vMsIij0uHswwwbXL8g==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@pnpm/exe.linux-x64@12.15.0':
|
||||
resolution: {integrity: sha512-yc+QlZR+9+nr29J4fRzhMoapgxTBDndq/+peyoq1XUufnQFnfbXUMFudDIKzHi545linGqmOmXfn0NoufNgcOg==}
|
||||
'@pnpm/exe.linux-x64@12.16.1':
|
||||
resolution: {integrity: sha512-RK//8ZLcXGW8A2k8bQOXZDBN1Qu92zN4dYnG1bzj1zoQ1fkZ6jNzZ1AvXtsfGVma795CJtvoaF5bLXf4l/FIUw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.15.0':
|
||||
resolution: {integrity: sha512-fPXZxuY1VclkOga9bc53qNCXcegfeoBR8+Ue+AospDwKzuGGSHll0oDxkLfkmpjyRqpe7bH5DyQPYp/xSMaIVA==}
|
||||
'@pnpm/exe.win32-arm64@12.16.1':
|
||||
resolution: {integrity: sha512-Ox3kNNVxtGw6pK7SVejmeOUgHhUTZgAM7NQQ6cU7frPw3ix9+j7d2MGbRg3fyEFAaKWq8ZAZLTQGcDyLnAu+qA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@pnpm/exe.win32-x64@12.15.0':
|
||||
resolution: {integrity: sha512-nrd+Tp1We1DGj4DGG5Bea9lZNh9y1PjlLBdEwdInS4D0h0UIDtM7JaGGGDJhNzIwcHASyihWI7HLV+dtG+fhYg==}
|
||||
'@pnpm/exe.win32-x64@12.16.1':
|
||||
resolution: {integrity: sha512-k+FhRJ0ttphFoFIVB1Sk4v76YJAulafmA/Ly57cysesUhhIzVlApeOShUz0jXubKx317TThlEfpQrukldnqORA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
pnpm@12.15.0:
|
||||
resolution: {integrity: sha512-s5UdbW/0qfE9x89WzSjSuOs328ycaOUuHhGRhz6p+CoBcoerULqtxczElPA2xnwOR+sTYIwvo29HcRth6hKOYQ==}
|
||||
pnpm@12.16.1:
|
||||
resolution: {integrity: sha512-CR0d7nknV1Zhsl2rPCrjHK/BxU1rce1qZgDPDByxBDSkR+rhQBXxsj4ua+3u1535SpMuIWTSgyKA3OP7MvZW0Q==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@pnpm/exe.android-arm64@12.15.0':
|
||||
'@pnpm/exe.android-arm64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.android-x64@12.15.0':
|
||||
'@pnpm/exe.android-x64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-arm64@12.15.0':
|
||||
'@pnpm/exe.darwin-arm64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.darwin-x64@12.15.0':
|
||||
'@pnpm/exe.darwin-x64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.freebsd-x64@12.15.0':
|
||||
'@pnpm/exe.freebsd-x64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64-musl@12.15.0':
|
||||
'@pnpm/exe.linux-arm64-musl@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-arm64@12.15.0':
|
||||
'@pnpm/exe.linux-arm64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-ppc64@12.15.0':
|
||||
'@pnpm/exe.linux-ppc64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-riscv64@12.15.0':
|
||||
'@pnpm/exe.linux-riscv64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-s390x@12.15.0':
|
||||
'@pnpm/exe.linux-s390x@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64-musl@12.15.0':
|
||||
'@pnpm/exe.linux-x64-musl@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.linux-x64@12.15.0':
|
||||
'@pnpm/exe.linux-x64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-arm64@12.15.0':
|
||||
'@pnpm/exe.win32-arm64@12.16.1':
|
||||
optional: true
|
||||
|
||||
'@pnpm/exe.win32-x64@12.15.0':
|
||||
'@pnpm/exe.win32-x64@12.16.1':
|
||||
optional: true
|
||||
|
||||
pnpm@12.15.0:
|
||||
pnpm@12.16.1:
|
||||
optionalDependencies:
|
||||
'@pnpm/exe.android-arm64': 12.15.0
|
||||
'@pnpm/exe.android-x64': 12.15.0
|
||||
'@pnpm/exe.darwin-arm64': 12.15.0
|
||||
'@pnpm/exe.darwin-x64': 12.15.0
|
||||
'@pnpm/exe.freebsd-x64': 12.15.0
|
||||
'@pnpm/exe.linux-arm64': 12.15.0
|
||||
'@pnpm/exe.linux-arm64-musl': 12.15.0
|
||||
'@pnpm/exe.linux-ppc64': 12.15.0
|
||||
'@pnpm/exe.linux-riscv64': 12.15.0
|
||||
'@pnpm/exe.linux-s390x': 12.15.0
|
||||
'@pnpm/exe.linux-x64': 12.15.0
|
||||
'@pnpm/exe.linux-x64-musl': 12.15.0
|
||||
'@pnpm/exe.win32-arm64': 12.15.0
|
||||
'@pnpm/exe.win32-x64': 12.15.0
|
||||
'@pnpm/exe.android-arm64': 12.16.1
|
||||
'@pnpm/exe.android-x64': 12.16.1
|
||||
'@pnpm/exe.darwin-arm64': 12.16.1
|
||||
'@pnpm/exe.darwin-x64': 12.16.1
|
||||
'@pnpm/exe.freebsd-x64': 12.16.1
|
||||
'@pnpm/exe.linux-arm64': 12.16.1
|
||||
'@pnpm/exe.linux-arm64-musl': 12.16.1
|
||||
'@pnpm/exe.linux-ppc64': 12.16.1
|
||||
'@pnpm/exe.linux-riscv64': 12.16.1
|
||||
'@pnpm/exe.linux-s390x': 12.16.1
|
||||
'@pnpm/exe.linux-x64': 12.16.1
|
||||
'@pnpm/exe.linux-x64-musl': 12.16.1
|
||||
'@pnpm/exe.win32-arm64': 12.16.1
|
||||
'@pnpm/exe.win32-x64': 12.16.1
|
||||
|
||||
---
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
@@ -266,13 +266,13 @@ async function getLatestReleaseVersion() {
|
||||
}
|
||||
|
||||
if (!META_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(`mihomo unsupported platform "${platform}-${arch}"`)
|
||||
throw new Error(`clash meta unsupported platform "${platform}-${arch}"`)
|
||||
}
|
||||
if (!META_ALPHA_MAP[`${platform}-${arch}`]) {
|
||||
throw new Error(`mihomo alpha unsupported platform "${platform}-${arch}"`)
|
||||
throw new Error(`clash meta alpha unsupported platform "${platform}-${arch}"`)
|
||||
}
|
||||
|
||||
function mihomoAlpha() {
|
||||
function clashMetaAlpha() {
|
||||
const name = META_ALPHA_MAP[`${platform}-${arch}`]
|
||||
const isWin = platform === 'win32'
|
||||
const urlExt = isWin ? 'zip' : 'gz'
|
||||
@@ -285,7 +285,7 @@ function mihomoAlpha() {
|
||||
}
|
||||
}
|
||||
|
||||
function mihomo() {
|
||||
function clashMeta() {
|
||||
const name = META_MAP[`${platform}-${arch}`]
|
||||
const isWin = platform === 'win32'
|
||||
const urlExt = isWin ? 'zip' : 'gz'
|
||||
@@ -733,12 +733,13 @@ const tasks = [
|
||||
{
|
||||
name: 'verge-mihomo-alpha',
|
||||
func: () =>
|
||||
getLatestAlphaVersion().then(() => resolveSidecar(mihomoAlpha())),
|
||||
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
|
||||
retry: 5,
|
||||
},
|
||||
{
|
||||
name: 'verge-mihomo',
|
||||
func: () => getLatestReleaseVersion().then(() => resolveSidecar(mihomo())),
|
||||
func: () =>
|
||||
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
|
||||
retry: 5,
|
||||
},
|
||||
// After both sidecar tasks: it hashes what they downloaded.
|
||||
|
||||
@@ -184,41 +184,84 @@ impl IClashTemp {
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
fn guard_port_value(config: &Mapping, key: &str, default: u16) -> u16 {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get(key)
|
||||
.get("redir-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(default);
|
||||
.unwrap_or(7895);
|
||||
if port == 0 {
|
||||
port = default;
|
||||
port = 7895;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn guard_redir_port(config: &Mapping) -> u16 {
|
||||
Self::guard_port_value(config, "redir-port", network::ports::DEFAULT_REDIR)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn guard_tproxy_port(config: &Mapping) -> u16 {
|
||||
Self::guard_port_value(config, "tproxy-port", network::ports::DEFAULT_TPROXY)
|
||||
let mut port = config
|
||||
.get("tproxy-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(network::ports::DEFAULT_TPROXY);
|
||||
if port == 0 {
|
||||
port = network::ports::DEFAULT_TPROXY;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
Self::guard_port_value(config, "mixed-port", network::ports::DEFAULT_MIXED)
|
||||
let raw_value = config.get("mixed-port");
|
||||
|
||||
let mut port = raw_value
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(network::ports::DEFAULT_MIXED);
|
||||
|
||||
if port == 0 {
|
||||
port = network::ports::DEFAULT_MIXED;
|
||||
}
|
||||
|
||||
port
|
||||
}
|
||||
|
||||
fn guard_socks_port(config: &Mapping) -> u16 {
|
||||
Self::guard_port_value(config, "socks-port", network::ports::DEFAULT_SOCKS)
|
||||
let mut port = config
|
||||
.get("socks-port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7898);
|
||||
if port == 0 {
|
||||
port = 7898;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
fn guard_port(config: &Mapping) -> u16 {
|
||||
Self::guard_port_value(config, "port", network::ports::DEFAULT_HTTP)
|
||||
let mut port = config
|
||||
.get("port")
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7899);
|
||||
if port == 0 {
|
||||
port = 7899;
|
||||
}
|
||||
port
|
||||
}
|
||||
|
||||
pub(super) fn guard_server_ctrl(config: &Mapping) -> String {
|
||||
|
||||
@@ -14,15 +14,6 @@ mod verge;
|
||||
pub(crate) use self::config::Config;
|
||||
pub use self::{clash::*, encrypt::*, mixed_port::*, prfitem::*, profiles::*, verge::*};
|
||||
|
||||
macro_rules! patch_opt_field {
|
||||
($lv:expr, $rv:expr, $key:tt) => {
|
||||
if ($rv.$key).is_some() {
|
||||
$lv.$key = $rv.$key.clone();
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use patch_opt_field;
|
||||
|
||||
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;";
|
||||
}
|
||||
|
||||
+108
-71
@@ -1,5 +1,3 @@
|
||||
use super::patch_opt_field;
|
||||
|
||||
use crate::{
|
||||
config::profiles,
|
||||
utils::{
|
||||
@@ -133,18 +131,19 @@ impl PrfOption {
|
||||
match (one, other) {
|
||||
(Some(a_ref), Some(b_ref)) => {
|
||||
let mut result = a_ref.clone();
|
||||
patch_opt_field!(result, b_ref, user_agent);
|
||||
patch_opt_field!(result, b_ref, with_proxy);
|
||||
patch_opt_field!(result, b_ref, self_proxy);
|
||||
patch_opt_field!(result, b_ref, danger_accept_invalid_certs);
|
||||
patch_opt_field!(result, b_ref, allow_auto_update);
|
||||
patch_opt_field!(result, b_ref, update_interval);
|
||||
patch_opt_field!(result, b_ref, merge);
|
||||
patch_opt_field!(result, b_ref, script);
|
||||
patch_opt_field!(result, b_ref, rules);
|
||||
patch_opt_field!(result, b_ref, proxies);
|
||||
patch_opt_field!(result, b_ref, groups);
|
||||
patch_opt_field!(result, b_ref, timeout_seconds);
|
||||
result.user_agent = b_ref.user_agent.clone().or(result.user_agent);
|
||||
result.with_proxy = b_ref.with_proxy.or(result.with_proxy);
|
||||
result.self_proxy = b_ref.self_proxy.or(result.self_proxy);
|
||||
result.danger_accept_invalid_certs =
|
||||
b_ref.danger_accept_invalid_certs.or(result.danger_accept_invalid_certs);
|
||||
result.allow_auto_update = b_ref.allow_auto_update.or(result.allow_auto_update);
|
||||
result.update_interval = b_ref.update_interval.or(result.update_interval);
|
||||
result.merge = b_ref.merge.clone().or(result.merge);
|
||||
result.script = b_ref.script.clone().or(result.script);
|
||||
result.rules = b_ref.rules.clone().or(result.rules);
|
||||
result.proxies = b_ref.proxies.clone().or(result.proxies);
|
||||
result.groups = b_ref.groups.clone().or(result.groups);
|
||||
result.timeout_seconds = b_ref.timeout_seconds.or(result.timeout_seconds);
|
||||
Some(result)
|
||||
}
|
||||
(Some(a_ref), None) => Some(a_ref.clone()),
|
||||
@@ -182,38 +181,6 @@ impl PrfItem {
|
||||
}
|
||||
}
|
||||
|
||||
async fn append_companion_item(item: &mut Self) -> Result<String> {
|
||||
profiles::profiles_append_item_safe(item).await?;
|
||||
item.uid
|
||||
.clone()
|
||||
.ok_or_else(|| anyhow::anyhow!("appended companion item has no uid"))
|
||||
}
|
||||
|
||||
async fn ensure_companion_items(
|
||||
merge: &mut Option<String>,
|
||||
script: &mut Option<String>,
|
||||
rules: &mut Option<String>,
|
||||
proxies: &mut Option<String>,
|
||||
groups: &mut Option<String>,
|
||||
) -> Result<()> {
|
||||
if merge.is_none() {
|
||||
*merge = Some(Self::append_companion_item(&mut Self::from_merge(None)).await?);
|
||||
}
|
||||
if script.is_none() {
|
||||
*script = Some(Self::append_companion_item(&mut Self::from_script(None)).await?);
|
||||
}
|
||||
if rules.is_none() {
|
||||
*rules = Some(Self::append_companion_item(&mut Self::from_rules()).await?);
|
||||
}
|
||||
if proxies.is_none() {
|
||||
*proxies = Some(Self::append_companion_item(&mut Self::from_proxies()).await?);
|
||||
}
|
||||
if groups.is_none() {
|
||||
*groups = Some(Self::append_companion_item(&mut Self::from_groups()).await?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn from_local(
|
||||
name: String,
|
||||
desc: String,
|
||||
@@ -230,7 +197,31 @@ impl PrfItem {
|
||||
let mut proxies = opt_ref.and_then(|o| o.proxies.clone());
|
||||
let mut groups = opt_ref.and_then(|o| o.groups.clone());
|
||||
|
||||
Self::ensure_companion_items(&mut merge, &mut script, &mut rules, &mut proxies, &mut groups).await?;
|
||||
if merge.is_none() {
|
||||
let merge_item = &mut Self::from_merge(None);
|
||||
profiles::profiles_append_item_safe(merge_item).await?;
|
||||
merge = merge_item.uid.clone();
|
||||
}
|
||||
if script.is_none() {
|
||||
let script_item = &mut Self::from_script(None);
|
||||
profiles::profiles_append_item_safe(script_item).await?;
|
||||
script = script_item.uid.clone();
|
||||
}
|
||||
if rules.is_none() {
|
||||
let rules_item = &mut Self::from_rules();
|
||||
profiles::profiles_append_item_safe(rules_item).await?;
|
||||
rules = rules_item.uid.clone();
|
||||
}
|
||||
if proxies.is_none() {
|
||||
let proxies_item = &mut Self::from_proxies();
|
||||
profiles::profiles_append_item_safe(proxies_item).await?;
|
||||
proxies = proxies_item.uid.clone();
|
||||
}
|
||||
if groups.is_none() {
|
||||
let groups_item = &mut Self::from_groups();
|
||||
profiles::profiles_append_item_safe(groups_item).await?;
|
||||
groups = groups_item.uid.clone();
|
||||
}
|
||||
Ok(Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("local".into()),
|
||||
@@ -384,7 +375,31 @@ impl PrfItem {
|
||||
bail!("profile does not contain `proxies` or `proxy-providers`");
|
||||
}
|
||||
|
||||
Self::ensure_companion_items(&mut merge, &mut script, &mut rules, &mut proxies, &mut groups).await?;
|
||||
if merge.is_none() {
|
||||
let merge_item = &mut Self::from_merge(None);
|
||||
profiles::profiles_append_item_safe(merge_item).await?;
|
||||
merge = merge_item.uid.clone();
|
||||
}
|
||||
if script.is_none() {
|
||||
let script_item = &mut Self::from_script(None);
|
||||
profiles::profiles_append_item_safe(script_item).await?;
|
||||
script = script_item.uid.clone();
|
||||
}
|
||||
if rules.is_none() {
|
||||
let rules_item = &mut Self::from_rules();
|
||||
profiles::profiles_append_item_safe(rules_item).await?;
|
||||
rules = rules_item.uid.clone();
|
||||
}
|
||||
if proxies.is_none() {
|
||||
let proxies_item = &mut Self::from_proxies();
|
||||
profiles::profiles_append_item_safe(proxies_item).await?;
|
||||
proxies = proxies_item.uid.clone();
|
||||
}
|
||||
if groups.is_none() {
|
||||
let groups_item = &mut Self::from_groups();
|
||||
profiles::profiles_append_item_safe(groups_item).await?;
|
||||
groups = groups_item.uid.clone();
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
uid: Some(uid),
|
||||
@@ -446,30 +461,46 @@ impl PrfItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn new_seq_item(prefix: &str, itype: &str, template: &str) -> Self {
|
||||
let uid = help::get_uid(prefix).into();
|
||||
fn from_rules() -> Self {
|
||||
let uid = help::get_uid("r").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some(itype.into()),
|
||||
itype: Some("rules".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(template.into()),
|
||||
file_data: Some(tmpl::ITEM_RULES.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_rules() -> Self {
|
||||
Self::new_seq_item("r", "rules", tmpl::ITEM_RULES)
|
||||
}
|
||||
|
||||
fn from_proxies() -> Self {
|
||||
Self::new_seq_item("p", "proxies", tmpl::ITEM_PROXIES)
|
||||
let uid = help::get_uid("p").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("proxies".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_PROXIES.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn from_groups() -> Self {
|
||||
Self::new_seq_item("g", "groups", tmpl::ITEM_GROUPS)
|
||||
let uid = help::get_uid("g").into();
|
||||
let file = format!("{uid}.yaml").into(); // yaml ext
|
||||
|
||||
Self {
|
||||
uid: Some(uid),
|
||||
itype: Some("groups".into()),
|
||||
file: Some(file),
|
||||
updated: Some(chrono::Local::now().timestamp() as usize),
|
||||
file_data: Some(tmpl::ITEM_GROUPS.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn read_file(&self) -> Result<String> {
|
||||
@@ -485,20 +516,26 @@ impl PrfItem {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! current_field {
|
||||
($name:ident, $field:ident) => {
|
||||
pub(crate) fn $name(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.$field.as_ref())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl PrfItem {
|
||||
current_field!(current_merge, merge);
|
||||
current_field!(current_script, script);
|
||||
current_field!(current_rules, rules);
|
||||
current_field!(current_proxies, proxies);
|
||||
current_field!(current_groups, groups);
|
||||
pub(crate) fn current_merge(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.merge.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn current_script(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.script.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn current_rules(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.rules.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn current_proxies(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.proxies.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn current_groups(&self) -> Option<&String> {
|
||||
self.option.as_ref().and_then(|o| o.groups.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
fn allow_auto_update_enabled(option: Option<&PrfOption>) -> bool {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
PrfOption, patch_opt_field,
|
||||
PrfOption,
|
||||
prfitem::{PrfItem, PrfSelected, normalize_profile_home_url},
|
||||
};
|
||||
use crate::core::notify::{Refresh, announce};
|
||||
@@ -86,6 +86,14 @@ impl ProfileDeletePlan {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! patch {
|
||||
($lv: expr, $rv: expr, $key: tt) => {
|
||||
if ($rv.$key).is_some() {
|
||||
$lv.$key = $rv.$key.to_owned();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl IProfiles {
|
||||
fn take_item_file_by_uid(items: &mut Vec<PrfItem>, target_uid: Option<&str>) -> Option<String> {
|
||||
let index = items.iter().position(|item| item.uid.as_deref() == target_uid)?;
|
||||
@@ -236,15 +244,15 @@ impl IProfiles {
|
||||
|
||||
for each in items.iter_mut() {
|
||||
if each.uid.as_deref() == Some(uid) {
|
||||
patch_opt_field!(each, item, itype);
|
||||
patch_opt_field!(each, item, name);
|
||||
patch_opt_field!(each, item, desc);
|
||||
patch_opt_field!(each, item, file);
|
||||
patch_opt_field!(each, item, url);
|
||||
patch_opt_field!(each, item, selected);
|
||||
patch_opt_field!(each, item, extra);
|
||||
patch_opt_field!(each, item, updated);
|
||||
patch_opt_field!(each, item, option);
|
||||
patch!(each, item, itype);
|
||||
patch!(each, item, name);
|
||||
patch!(each, item, desc);
|
||||
patch!(each, item, file);
|
||||
patch!(each, item, url);
|
||||
patch!(each, item, selected);
|
||||
patch!(each, item, extra);
|
||||
patch!(each, item, updated);
|
||||
patch!(each, item, option);
|
||||
|
||||
self.items = Some(items);
|
||||
return self.save_file().await;
|
||||
@@ -412,21 +420,6 @@ pub(crate) async fn profiles_append_item_with_filedata_safe(item: &PrfItem, file
|
||||
profiles_append_item_safe(item).await
|
||||
}
|
||||
|
||||
/// Applies `edit` to the committed profiles; commits only when the edit succeeds.
|
||||
async fn with_profiles_edit<F, Fut>(edit: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce(IProfiles) -> Fut + Send,
|
||||
Fut: std::future::Future<Output = Result<IProfiles>> + Send,
|
||||
{
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|profiles| async move {
|
||||
let profiles = edit(profiles).await?;
|
||||
Ok((profiles, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn profiles_append_item_safe(item: &mut PrfItem) -> Result<()> {
|
||||
let profiles = Config::profiles().await;
|
||||
profiles_append_item_to_safe(&profiles, item).await
|
||||
@@ -442,19 +435,43 @@ pub(super) async fn profiles_append_item_to_safe(profiles: &Draft<IProfiles>, it
|
||||
}
|
||||
|
||||
pub(crate) async fn profiles_patch_item_safe(index: &str, item: &PrfItem) -> Result<()> {
|
||||
with_profiles_edit(|mut profiles| async move { profiles.patch_item(index, item).await.map(|_| profiles) }).await
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
profiles.patch_item(index, item).await?;
|
||||
Ok((profiles, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn profiles_reorder_safe(active_id: &str, over_id: &str) -> Result<()> {
|
||||
with_profiles_edit(|mut profiles| async move { profiles.reorder(active_id, over_id).await.map(|_| profiles) }).await
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
profiles.reorder(active_id, over_id).await?;
|
||||
Ok((profiles, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn profiles_save_file_safe() -> Result<()> {
|
||||
with_profiles_edit(|profiles| async move { profiles.save_file().await.map(|_| profiles) }).await
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|profiles| async move {
|
||||
profiles.save_file().await?;
|
||||
Ok((profiles, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn profiles_update_item_safe(index: &str, item: &mut PrfItem) -> Result<()> {
|
||||
with_profiles_edit(|mut profiles| async move { profiles.update_item(index, item).await.map(|_| profiles) }).await
|
||||
Config::profiles()
|
||||
.await
|
||||
.with_data_modify(|mut profiles| async move {
|
||||
profiles.update_item(index, item).await?;
|
||||
Ok((profiles, ()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -696,21 +713,19 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn current_item_mut(profiles: &mut IProfiles) -> Option<&mut PrfItem> {
|
||||
let current = profiles.current.clone()?;
|
||||
profiles
|
||||
.items
|
||||
.as_mut()?
|
||||
.iter_mut()
|
||||
.find(|item| item.uid.as_ref() == Some(¤t))
|
||||
}
|
||||
|
||||
/// Records a backend-made selection so the next core start restores it.
|
||||
pub(crate) async fn record_selected_node(group_name: &str, node: &str) -> Result<()> {
|
||||
let group_name = String::from(group_name);
|
||||
let node = String::from(node);
|
||||
persist_selected_update(true, move |mut profiles| async move {
|
||||
let Some(item) = current_item_mut(&mut profiles) else {
|
||||
let Some(current) = profiles.current.clone() else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
let Some(item) = profiles
|
||||
.items
|
||||
.as_mut()
|
||||
.and_then(|items| items.iter_mut().find(|item| item.uid.as_ref() == Some(¤t)))
|
||||
else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
|
||||
@@ -745,7 +760,14 @@ fn remove_selected_node(selected: &mut Vec<PrfSelected>, group_name: &str) -> bo
|
||||
|
||||
pub(crate) async fn forget_selected_node(group_name: &str) -> Result<()> {
|
||||
persist_selected_update(true, move |mut profiles| async move {
|
||||
let Some(item) = current_item_mut(&mut profiles) else {
|
||||
let Some(current) = profiles.current.clone() else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
let Some(item) = profiles
|
||||
.items
|
||||
.as_mut()
|
||||
.and_then(|items| items.iter_mut().find(|item| item.uid.as_ref() == Some(¤t)))
|
||||
else {
|
||||
return Ok((profiles, false));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::Config;
|
||||
use crate::{
|
||||
config::{DEFAULT_PAC, deserialize_encrypted, patch_opt_field, serialize_encrypted},
|
||||
config::{DEFAULT_PAC, deserialize_encrypted, serialize_encrypted},
|
||||
constants::network,
|
||||
utils::{dirs, help},
|
||||
};
|
||||
@@ -39,7 +39,7 @@ pub struct IVerge {
|
||||
|
||||
pub traffic_graph: Option<bool>,
|
||||
|
||||
/// show memory info (only for Mihomo)
|
||||
/// show memory info (only for Clash Meta)
|
||||
pub enable_memory_usage: Option<bool>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -432,90 +432,98 @@ impl IVerge {
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn patch_config(&mut self, patch: &Self) {
|
||||
patch_opt_field!(self, patch, app_log_level);
|
||||
patch_opt_field!(self, patch, app_log_max_size);
|
||||
patch_opt_field!(self, patch, app_log_max_count);
|
||||
macro_rules! patch {
|
||||
($key: tt) => {
|
||||
if patch.$key.is_some() {
|
||||
self.$key = patch.$key.clone();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
patch_opt_field!(self, patch, language);
|
||||
patch_opt_field!(self, patch, theme_mode);
|
||||
patch_opt_field!(self, patch, tray_event);
|
||||
patch_opt_field!(self, patch, env_type);
|
||||
patch_opt_field!(self, patch, start_page);
|
||||
patch_opt_field!(self, patch, startup_script);
|
||||
patch_opt_field!(self, patch, traffic_graph);
|
||||
patch_opt_field!(self, patch, enable_memory_usage);
|
||||
patch_opt_field!(self, patch, enable_group_icon);
|
||||
patch_opt_field!(self, patch, pause_render_traffic_stats_on_blur);
|
||||
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_opt_field!(self, patch, tray_icon);
|
||||
patch_opt_field!(self, patch, menu_icon);
|
||||
patch_opt_field!(self, patch, menu_order);
|
||||
patch_opt_field!(self, patch, proxy_group_tools_position);
|
||||
patch_opt_field!(self, patch, notice_position);
|
||||
patch_opt_field!(self, patch, collapse_navbar);
|
||||
patch_opt_field!(self, patch, common_tray_icon);
|
||||
patch_opt_field!(self, patch, sysproxy_tray_icon);
|
||||
patch_opt_field!(self, patch, tun_tray_icon);
|
||||
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_opt_field!(self, patch, enable_tun_mode);
|
||||
patch_opt_field!(self, patch, enable_auto_launch);
|
||||
patch_opt_field!(self, patch, enable_silent_start);
|
||||
patch_opt_field!(self, patch, enable_hover_jump_navigator);
|
||||
patch_opt_field!(self, patch, hover_jump_navigator_delay);
|
||||
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_opt_field!(self, patch, verge_redir_port);
|
||||
patch!(verge_redir_port);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
patch_opt_field!(self, patch, verge_redir_enabled);
|
||||
patch!(verge_redir_enabled);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch_opt_field!(self, patch, verge_tproxy_port);
|
||||
patch!(verge_tproxy_port);
|
||||
#[cfg(target_os = "linux")]
|
||||
patch_opt_field!(self, patch, verge_tproxy_enabled);
|
||||
patch_opt_field!(self, patch, verge_mixed_port);
|
||||
patch_opt_field!(self, patch, verge_socks_port);
|
||||
patch_opt_field!(self, patch, verge_socks_enabled);
|
||||
patch_opt_field!(self, patch, verge_port);
|
||||
patch_opt_field!(self, patch, verge_http_enabled);
|
||||
patch_opt_field!(self, patch, enable_system_proxy);
|
||||
patch_opt_field!(self, patch, enable_proxy_guard);
|
||||
patch_opt_field!(self, patch, enable_bypass_check);
|
||||
patch_opt_field!(self, patch, use_default_bypass);
|
||||
patch_opt_field!(self, patch, system_proxy_bypass);
|
||||
patch_opt_field!(self, patch, proxy_guard_duration);
|
||||
patch_opt_field!(self, patch, proxy_auto_config);
|
||||
patch_opt_field!(self, patch, pac_file_content);
|
||||
patch_opt_field!(self, patch, proxy_host);
|
||||
patch_opt_field!(self, patch, theme_setting);
|
||||
patch_opt_field!(self, patch, web_ui_list);
|
||||
patch_opt_field!(self, patch, clash_core);
|
||||
patch_opt_field!(self, patch, hotkeys);
|
||||
patch_opt_field!(self, patch, enable_global_hotkey);
|
||||
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_opt_field!(self, patch, auto_close_connection);
|
||||
patch_opt_field!(self, patch, auto_check_update);
|
||||
patch_opt_field!(self, patch, default_latency_test);
|
||||
patch_opt_field!(self, patch, default_latency_timeout);
|
||||
patch_opt_field!(self, patch, enable_auto_delay_detection);
|
||||
patch_opt_field!(self, patch, auto_delay_detection_interval_minutes);
|
||||
patch_opt_field!(self, patch, enable_builtin_enhanced);
|
||||
patch_opt_field!(self, patch, proxy_layout_column);
|
||||
patch_opt_field!(self, patch, test_list);
|
||||
patch_opt_field!(self, patch, auto_log_clean);
|
||||
patch_opt_field!(self, patch, enable_auto_backup_schedule);
|
||||
patch_opt_field!(self, patch, auto_backup_interval_hours);
|
||||
patch_opt_field!(self, patch, auto_backup_on_change);
|
||||
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_opt_field!(self, patch, webdav_url);
|
||||
patch_opt_field!(self, patch, webdav_username);
|
||||
patch_opt_field!(self, patch, webdav_password);
|
||||
patch!(webdav_url);
|
||||
patch!(webdav_username);
|
||||
patch!(webdav_password);
|
||||
#[cfg(target_os = "macos")]
|
||||
patch_opt_field!(self, patch, enable_tray_speed);
|
||||
patch_opt_field!(self, patch, tray_proxy_groups_display_mode);
|
||||
patch_opt_field!(self, patch, tray_inline_outbound_modes);
|
||||
patch_opt_field!(self, patch, enable_auto_light_weight_mode);
|
||||
patch_opt_field!(self, patch, auto_light_weight_minutes);
|
||||
patch_opt_field!(self, patch, enable_dns_settings);
|
||||
patch_opt_field!(self, patch, home_cards);
|
||||
patch_opt_field!(self, patch, enable_external_controller);
|
||||
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 {
|
||||
|
||||
@@ -14,13 +14,6 @@ pub mod network {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod sysproxy {
|
||||
/// Single source of the macOS default bypass list; both proxy backends compose from it.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const MACOS_DEFAULT_BYPASS: &str =
|
||||
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
||||
}
|
||||
|
||||
pub mod timing {
|
||||
use super::Duration;
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ use crate::{
|
||||
use anyhow::Result;
|
||||
use chrono::Local;
|
||||
use clash_verge_logging::{Type, logging};
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
OnceLock,
|
||||
Arc, OnceLock,
|
||||
atomic::{AtomicBool, AtomicI64, Ordering},
|
||||
},
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
@@ -81,9 +82,8 @@ impl Default for AutoBackupSettings {
|
||||
}
|
||||
|
||||
pub struct AutoBackupManager {
|
||||
settings: Arc<RwLock<AutoBackupSettings>>,
|
||||
settings_tx: watch::Sender<AutoBackupSettings>,
|
||||
// The watch channel is the single settings store; no duplicate copies.
|
||||
settings_rx: watch::Receiver<AutoBackupSettings>,
|
||||
runner_started: AtomicBool,
|
||||
exec_lock: Mutex<()>,
|
||||
last_backup: AtomicI64,
|
||||
@@ -93,10 +93,10 @@ impl AutoBackupManager {
|
||||
pub fn global() -> &'static Self {
|
||||
static INSTANCE: OnceLock<AutoBackupManager> = OnceLock::new();
|
||||
INSTANCE.get_or_init(|| {
|
||||
let (settings_tx, settings_rx) = watch::channel(AutoBackupSettings::default());
|
||||
let (tx, _rx) = watch::channel(AutoBackupSettings::default());
|
||||
Self {
|
||||
settings_tx,
|
||||
settings_rx,
|
||||
settings: Arc::new(RwLock::new(AutoBackupSettings::default())),
|
||||
settings_tx: tx,
|
||||
runner_started: AtomicBool::new(false),
|
||||
exec_lock: Mutex::new(()),
|
||||
last_backup: AtomicI64::new(0),
|
||||
@@ -106,6 +106,9 @@ impl AutoBackupManager {
|
||||
|
||||
pub async fn init(&self) -> Result<()> {
|
||||
let settings = Self::load_settings().await;
|
||||
{
|
||||
*self.settings.write() = settings;
|
||||
}
|
||||
let _ = self.settings_tx.send(settings);
|
||||
self.maybe_start_runner(settings);
|
||||
Ok(())
|
||||
@@ -113,6 +116,9 @@ impl AutoBackupManager {
|
||||
|
||||
pub async fn refresh_settings(&self) -> Result<()> {
|
||||
let settings = Self::load_settings().await;
|
||||
{
|
||||
*self.settings.write() = settings;
|
||||
}
|
||||
let _ = self.settings_tx.send(settings);
|
||||
self.maybe_start_runner(settings);
|
||||
Ok(())
|
||||
@@ -187,7 +193,7 @@ impl AutoBackupManager {
|
||||
}
|
||||
|
||||
async fn execute_trigger(&self, trigger: AutoBackupTrigger) -> Result<()> {
|
||||
let snapshot = *self.settings_rx.borrow();
|
||||
let snapshot = *self.settings.read();
|
||||
|
||||
if trigger.is_schedule() && !snapshot.schedule_enabled {
|
||||
return Ok(());
|
||||
|
||||
@@ -466,3 +466,12 @@ impl Hotkey {
|
||||
(del_list, add_list)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Hotkey {
|
||||
fn drop(&mut self) {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
if let Err(e) = app_handle.global_shortcut().unregister_all() {
|
||||
logging!(error, Type::Hotkey, "Error unregistering all hotkeys: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,47 +40,8 @@ impl LightweightState {
|
||||
|
||||
static LIGHTWEIGHT_STATE: AtomicU8 = AtomicU8::new(LightweightState::Normal as u8);
|
||||
|
||||
struct WindowListener {
|
||||
handler_id: AtomicU32,
|
||||
event: &'static str,
|
||||
label: &'static str,
|
||||
}
|
||||
|
||||
impl WindowListener {
|
||||
fn setup(&self, on_event: impl Fn() + Send + 'static) {
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
let previous_handler_id = self.handler_id.swap(0, Ordering::AcqRel);
|
||||
if previous_handler_id != 0 {
|
||||
window.unlisten(previous_handler_id);
|
||||
logging!(debug, Type::Lightweight, "覆盖旧的{}监听", self.label);
|
||||
}
|
||||
let handler_id = window.listen(self.event, move |_event| on_event());
|
||||
self.handler_id.store(handler_id, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(&self) {
|
||||
let id = self.handler_id.swap(0, Ordering::AcqRel);
|
||||
if id != 0 {
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
window.unlisten(id);
|
||||
}
|
||||
logging!(debug, Type::Lightweight, "取消了{}监听", self.label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static WINDOW_CLOSE_LISTENER: WindowListener = WindowListener {
|
||||
handler_id: AtomicU32::new(0),
|
||||
event: "tauri://close-requested",
|
||||
label: "窗口关闭",
|
||||
};
|
||||
|
||||
static WEBVIEW_FOCUS_LISTENER: WindowListener = WindowListener {
|
||||
handler_id: AtomicU32::new(0),
|
||||
event: "tauri://focus",
|
||||
label: "窗口焦点",
|
||||
};
|
||||
static WINDOW_CLOSE_HANDLER_ID: AtomicU32 = AtomicU32::new(0);
|
||||
static WEBVIEW_FOCUS_HANDLER_ID: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
static CANCEL_TX: Mutex<Option<oneshot::Sender<()>>> = Mutex::new(None);
|
||||
|
||||
@@ -193,27 +154,55 @@ pub async fn add_light_weight_timer() {
|
||||
}
|
||||
|
||||
fn setup_window_close_listener() {
|
||||
WINDOW_CLOSE_LISTENER.setup(|| {
|
||||
std::mem::drop(AsyncHandler::spawn(|| async {
|
||||
setup_light_weight_timer().await;
|
||||
}));
|
||||
logging!(info, Type::Lightweight, "监听到关闭请求,开始轻量模式计时");
|
||||
});
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
let previous_handler_id = WINDOW_CLOSE_HANDLER_ID.swap(0, Ordering::AcqRel);
|
||||
if previous_handler_id != 0 {
|
||||
window.unlisten(previous_handler_id);
|
||||
logging!(debug, Type::Lightweight, "覆盖旧的窗口关闭监听");
|
||||
}
|
||||
let handler_id = window.listen("tauri://close-requested", move |_event| {
|
||||
std::mem::drop(AsyncHandler::spawn(|| async {
|
||||
setup_light_weight_timer().await;
|
||||
}));
|
||||
logging!(info, Type::Lightweight, "监听到关闭请求,开始轻量模式计时");
|
||||
});
|
||||
WINDOW_CLOSE_HANDLER_ID.store(handler_id, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_window_close_listener() {
|
||||
WINDOW_CLOSE_LISTENER.cancel();
|
||||
let id = WINDOW_CLOSE_HANDLER_ID.swap(0, Ordering::AcqRel);
|
||||
if id != 0 {
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
window.unlisten(id);
|
||||
}
|
||||
logging!(debug, Type::Lightweight, "取消了窗口关闭监听");
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_webview_focus_listener() {
|
||||
WEBVIEW_FOCUS_LISTENER.setup(|| {
|
||||
cancel_light_weight_timer();
|
||||
logging!(debug, Type::Lightweight, "监听到窗口获得焦点,取消轻量模式计时");
|
||||
});
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
let previous_handler_id = WEBVIEW_FOCUS_HANDLER_ID.swap(0, Ordering::AcqRel);
|
||||
if previous_handler_id != 0 {
|
||||
window.unlisten(previous_handler_id);
|
||||
logging!(debug, Type::Lightweight, "覆盖旧的窗口焦点监听");
|
||||
}
|
||||
let handler_id = window.listen("tauri://focus", move |_event| {
|
||||
cancel_light_weight_timer();
|
||||
logging!(debug, Type::Lightweight, "监听到窗口获得焦点,取消轻量模式计时");
|
||||
});
|
||||
WEBVIEW_FOCUS_HANDLER_ID.store(handler_id, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel_webview_focus_listener() {
|
||||
WEBVIEW_FOCUS_LISTENER.cancel();
|
||||
let id = WEBVIEW_FOCUS_HANDLER_ID.swap(0, Ordering::AcqRel);
|
||||
if id != 0 {
|
||||
if let Some(window) = WindowManager::get_main_window() {
|
||||
window.unlisten(id);
|
||||
}
|
||||
logging!(debug, Type::Lightweight, "取消了窗口焦点监听");
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_light_weight_timer() {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
notification::{self, FailedOperation},
|
||||
runstate::RUN_STATE,
|
||||
service,
|
||||
sysopt::{Sysopt, format_bypass},
|
||||
sysopt::Sysopt,
|
||||
},
|
||||
process::AsyncHandler,
|
||||
utils::{
|
||||
@@ -204,6 +204,8 @@ const fn refusal_classification(service_usable: Option<bool>) -> SysproxyFailure
|
||||
}
|
||||
|
||||
const LOOPBACK_HOST: &str = "127.0.0.1";
|
||||
const MACOS_DEFAULT_BYPASS: &str =
|
||||
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
||||
const MAX_SERVICE_BYPASS_LEN: usize = 8192;
|
||||
|
||||
static SERVICE_PROXY_OPERATIONS: ServiceProxyOperations = ServiceProxyOperations::new();
|
||||
@@ -325,7 +327,13 @@ fn service_bypass(verge: &IVerge) -> Result<String> {
|
||||
let custom = verge.system_proxy_bypass.as_deref().unwrap_or("");
|
||||
ensure!(!custom.contains('\0'), "system proxy bypass contains NUL");
|
||||
|
||||
let mut bypass: String = format_bypass(verge.use_default_bypass.unwrap_or(true), custom).into();
|
||||
let mut bypass = if custom.is_empty() {
|
||||
MACOS_DEFAULT_BYPASS.to_owned()
|
||||
} else if verge.use_default_bypass.unwrap_or(true) {
|
||||
format!("{MACOS_DEFAULT_BYPASS},{custom}")
|
||||
} else {
|
||||
custom.to_owned()
|
||||
};
|
||||
truncate_utf8(&mut bypass, MAX_SERVICE_BYPASS_LEN);
|
||||
Ok(bypass)
|
||||
}
|
||||
|
||||
@@ -593,71 +593,50 @@ fn packaged_service_tool_path(file_name: &str, packaged_path: impl FnOnce() -> R
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(super) fn run_maybe_elevated(program: &Path, hide_window: bool) -> Result<std::process::ExitStatus> {
|
||||
fn uninstall_service() -> Result<()> {
|
||||
logging!(info, Type::Service, "uninstall service");
|
||||
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => {
|
||||
let mut command = RunasCommand::new(program);
|
||||
if hide_window {
|
||||
command.show(false);
|
||||
}
|
||||
command.status()?
|
||||
}
|
||||
_ => {
|
||||
let mut command = StdCommand::new(program);
|
||||
if hide_window {
|
||||
// 0x08000000 is CREATE_NO_WINDOW, the direct counterpart of runas's show(false).
|
||||
command.creation_flags(0x08000000);
|
||||
}
|
||||
command.status()?
|
||||
}
|
||||
};
|
||||
Ok(status)
|
||||
}
|
||||
let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall.exe", || {
|
||||
Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall.exe"))
|
||||
})?;
|
||||
|
||||
fn uninstaller_tool_path(file_name: &str, packaged_path: impl FnOnce() -> Result<PathBuf>) -> Result<PathBuf> {
|
||||
let uninstall_path = packaged_service_tool_path(file_name, packaged_path)?;
|
||||
if !uninstall_path.exists() {
|
||||
bail!(format!("uninstaller not found: {uninstall_path:?}"));
|
||||
}
|
||||
Ok(uninstall_path)
|
||||
}
|
||||
|
||||
fn ensure_uninstall_success(status: &std::process::ExitStatus) -> Result<()> {
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
let status = match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
|
||||
_ => StdCommand::new(uninstall_path).creation_flags(0x08000000).status()?,
|
||||
};
|
||||
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to uninstall service with status {}",
|
||||
status.code().unwrap_or(-1)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn uninstall_service() -> Result<()> {
|
||||
logging!(info, Type::Service, "uninstall service");
|
||||
|
||||
let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall.exe", || {
|
||||
Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall.exe"))
|
||||
})?;
|
||||
|
||||
let status = run_maybe_elevated(&uninstall_path, true)?;
|
||||
ensure_uninstall_success(&status)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn uninstall_service() -> Result<()> {
|
||||
logging!(info, Type::Service, "uninstall service");
|
||||
|
||||
let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall", || {
|
||||
let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall", || {
|
||||
Ok(tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-uninstall"))
|
||||
})?;
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
bail!(format!("uninstaller not found: {uninstall_path:?}"));
|
||||
}
|
||||
|
||||
let elevator = crate::utils::help::linux_elevator();
|
||||
let status = if linux_running_as_root() {
|
||||
StdCommand::new(&uninstall_path).status()?
|
||||
@@ -687,7 +666,14 @@ fn uninstall_service() -> Result<()> {
|
||||
status.code().unwrap_or(-1)
|
||||
);
|
||||
|
||||
ensure_uninstall_success(&status)
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to uninstall service with status {}",
|
||||
status.code().unwrap_or(-1)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -702,10 +688,14 @@ fn linux_running_as_root() -> bool {
|
||||
fn uninstall_service() -> Result<()> {
|
||||
logging!(info, Type::Service, "uninstall service");
|
||||
|
||||
let uninstall_path = uninstaller_tool_path("clash-verge-service-uninstall", || {
|
||||
let uninstall_path = packaged_service_tool_path("clash-verge-service-uninstall", || {
|
||||
Ok(dirs::service_path()?.with_file_name("clash-verge-service-uninstall"))
|
||||
})?;
|
||||
|
||||
if !uninstall_path.exists() {
|
||||
bail!(format!("uninstaller not found: {uninstall_path:?}"));
|
||||
}
|
||||
|
||||
let uninstall_path = macos_service_tool_path(&uninstall_path)?;
|
||||
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
|
||||
|
||||
@@ -721,7 +711,14 @@ fn uninstall_service() -> Result<()> {
|
||||
|
||||
let status = StdCommand::new("osascript").args(vec!["-e", &command]).status()?;
|
||||
|
||||
ensure_uninstall_success(&status)
|
||||
if !status.success() {
|
||||
bail!(
|
||||
"failed to uninstall service with status {}",
|
||||
status.code().unwrap_or(-1)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_service() -> Result<()> {
|
||||
|
||||
@@ -241,19 +241,20 @@ async fn current_os_proxy_state(sys: Sysproxy, auto: Autoproxy) -> OsProxyState
|
||||
}
|
||||
|
||||
pub(crate) struct Sysopt {
|
||||
// Owns the proxy target pair; every reader and writer goes through this lock.
|
||||
proxy_state_lock: TokioMutex<(Sysproxy, Autoproxy)>,
|
||||
update_lock: TokioMutex<()>,
|
||||
guard_operation_lock: TokioMutex<()>,
|
||||
reset_sysproxy: AtomicBool,
|
||||
inner_proxy: Arc<RwLock<(Sysproxy, Autoproxy)>>,
|
||||
guard: Arc<RwLock<GuardMonitor>>,
|
||||
}
|
||||
|
||||
impl Default for Sysopt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
proxy_state_lock: TokioMutex::new((Sysproxy::default(), Autoproxy::default())),
|
||||
update_lock: TokioMutex::new(()),
|
||||
guard_operation_lock: TokioMutex::new(()),
|
||||
reset_sysproxy: AtomicBool::new(false),
|
||||
inner_proxy: Arc::new(RwLock::new((Sysproxy::default(), Autoproxy::default()))),
|
||||
guard: Arc::new(RwLock::new(GuardMonitor::new(GuardType::None, Duration::from_secs(30)))),
|
||||
}
|
||||
}
|
||||
@@ -268,9 +269,10 @@ static DEFAULT_BYPASS: &str = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
static BYPASS_SEPARATOR: &str = ",";
|
||||
#[cfg(target_os = "macos")]
|
||||
static DEFAULT_BYPASS: &str = crate::constants::sysproxy::MACOS_DEFAULT_BYPASS;
|
||||
static DEFAULT_BYPASS: &str =
|
||||
"127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,localhost,*.local,*.crashlytics.com,<local>";
|
||||
|
||||
pub(super) fn format_bypass(use_default: bool, custom_bypass: &str) -> String {
|
||||
fn format_bypass(use_default: bool, custom_bypass: &str) -> String {
|
||||
if custom_bypass.is_empty() {
|
||||
DEFAULT_BYPASS.into()
|
||||
} else if use_default {
|
||||
@@ -359,7 +361,7 @@ impl Sysopt {
|
||||
}
|
||||
}
|
||||
while self.access_guard().read().get_state().is_pendding() {
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
// `start()` false may also mean a guard was already running.
|
||||
!self.access_guard().read().get_state().is_stopped()
|
||||
@@ -407,21 +409,18 @@ impl Sysopt {
|
||||
/// configuration instead of a partially-applied one (e.g. SOCKS already
|
||||
/// disabled but HTTP still enabled mid-transition).
|
||||
pub(crate) async fn wait_idle(&self) {
|
||||
let _ = self.proxy_state_lock.lock().await;
|
||||
let _ = self.update_lock.lock().await;
|
||||
}
|
||||
|
||||
/// init the sysproxy
|
||||
#[allow(
|
||||
clippy::significant_drop_tightening,
|
||||
reason = "the proxy state lock must stay held until the OS write finishes; wait_idle relies on it"
|
||||
)]
|
||||
pub(super) async fn update_sysproxy(&self) -> Result<()> {
|
||||
let mut proxy_state = self.proxy_state_lock.lock().await;
|
||||
let _lock = self.update_lock.lock().await;
|
||||
let verge = Config::verge().await.latest_arc();
|
||||
// Configured, not live: this runs while the Core is being started or restarted, and
|
||||
// asking a Core that is not up yet would only fall back here anyway.
|
||||
let port = MixedPort::desired().await;
|
||||
let pac_port = server::embedded_server_port()?;
|
||||
// 先 await, 避免持有锁导致的 Send 问题
|
||||
let bypass = get_bypass().await;
|
||||
|
||||
let (sys_enable, pac_enable, proxy_host, proxy_guard) = (
|
||||
@@ -432,7 +431,7 @@ impl Sysopt {
|
||||
);
|
||||
|
||||
let (sys, auto, guard_type) = {
|
||||
let (sys, auto) = &mut *proxy_state;
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.host = proxy_host.into();
|
||||
sys.port = port;
|
||||
sys.bypass = bypass.into();
|
||||
@@ -529,10 +528,6 @@ impl Sysopt {
|
||||
}
|
||||
|
||||
/// reset the sysproxy
|
||||
#[allow(
|
||||
clippy::significant_drop_tightening,
|
||||
reason = "the proxy state lock must stay held until the disable finishes; wait_idle relies on it"
|
||||
)]
|
||||
pub(super) async fn reset_sysproxy(&self) -> Result<()> {
|
||||
if self
|
||||
.reset_sysproxy
|
||||
@@ -544,13 +539,13 @@ impl Sysopt {
|
||||
defer! {
|
||||
self.reset_sysproxy.store(false, Ordering::SeqCst);
|
||||
}
|
||||
let mut proxy_state = self.proxy_state_lock.lock().await;
|
||||
let _lock = self.update_lock.lock().await;
|
||||
let _guard_operation = self.guard_operation_lock.lock().await;
|
||||
let drained = self.stop_proxy_guard_locked().await;
|
||||
|
||||
// 直接关闭所有代理
|
||||
let (sys, auto) = {
|
||||
let (sys, auto) = &mut *proxy_state;
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
auto.enable = false;
|
||||
(sys.clone(), auto.clone())
|
||||
|
||||
@@ -636,8 +636,7 @@ async fn create_tray_menu(
|
||||
.tray_proxy_groups_display_mode
|
||||
.as_deref()
|
||||
.unwrap_or("default");
|
||||
let include_proxy_groups =
|
||||
options.include_proxy_groups && verge_settings.tray_proxy_groups_display_mode.as_deref() != Some("disable");
|
||||
let include_proxy_groups = options.include_proxy_groups && tray_proxy_groups_display_mode != "disable";
|
||||
|
||||
let proxy_group_order_map = runtime_proxy_groups_order;
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
use crate::{core::service, utils::dirs};
|
||||
use crate::utils::dirs;
|
||||
use anyhow::{Result, bail};
|
||||
use deelevate::{PrivilegeLevel, Token};
|
||||
use runas::Command as RunasCommand;
|
||||
use std::process::Command as StdCommand;
|
||||
|
||||
pub fn invoke_uwptools() -> Result<()> {
|
||||
let resource_dir = dirs::app_resources_dir()?;
|
||||
@@ -11,7 +14,13 @@ pub fn invoke_uwptools() -> Result<()> {
|
||||
bail!("enableLoopback exe not found");
|
||||
}
|
||||
|
||||
service::run_maybe_elevated(&tool_path, false)?;
|
||||
let token = Token::with_current_process()?;
|
||||
let level = token.privilege_level()?;
|
||||
|
||||
match level {
|
||||
PrivilegeLevel::NotPrivileged => RunasCommand::new(tool_path).status()?,
|
||||
_ => StdCommand::new(tool_path).status()?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// This function is exported for use by the Clash core
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
function main(config, _name) {
|
||||
if (config.mode === 'script') {
|
||||
config.mode = 'rule'
|
||||
}
|
||||
return config
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// This function is exported for use by the Clash core
|
||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||
function main(config, _name) {
|
||||
if (Array.isArray(config.proxies)) {
|
||||
config.proxies.forEach((p, i) => {
|
||||
if (p.type === 'hysteria' && typeof p.alpn === 'string') {
|
||||
config.proxies[i].alpn = [p.alpn]
|
||||
}
|
||||
})
|
||||
}
|
||||
return config
|
||||
}
|
||||
@@ -22,6 +22,12 @@ pub enum ChainType {
|
||||
Groups(SeqMap),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ChainSupport {
|
||||
ClashMeta,
|
||||
ClashMetaAlpha,
|
||||
}
|
||||
|
||||
// Helper trait to allow async conversion
|
||||
pub trait AsyncChainItemFrom {
|
||||
async fn from_async(item: &PrfItem) -> Option<ChainItem>;
|
||||
@@ -47,16 +53,70 @@ impl AsyncChainItemFrom for Option<ChainItem> {
|
||||
uid,
|
||||
data: ChainType::Merge(help::read_mapping(&path).await.ok()?),
|
||||
}),
|
||||
"rules" | "proxies" | "groups" => {
|
||||
"rules" => {
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
let data = match itype {
|
||||
"rules" => ChainType::Rules(seq_map),
|
||||
"proxies" => ChainType::Proxies(seq_map),
|
||||
_ => ChainType::Groups(seq_map),
|
||||
};
|
||||
Some(ChainItem { uid, data })
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Rules(seq_map),
|
||||
})
|
||||
}
|
||||
"proxies" => {
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Proxies(seq_map),
|
||||
})
|
||||
}
|
||||
"groups" => {
|
||||
let seq_map = help::read_yaml::<SeqMap>(&path).await.ok()?;
|
||||
Some(ChainItem {
|
||||
uid,
|
||||
data: ChainType::Groups(seq_map),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ChainItem {
|
||||
/// 内建支持一些脚本
|
||||
pub fn builtin() -> Vec<(ChainSupport, Self)> {
|
||||
// meta 的一些处理
|
||||
let meta_guard = Self::to_script("verge_meta_guard", include_str!("./builtin/meta_guard.js"));
|
||||
|
||||
// meta 1.13.2 alpn string 转 数组
|
||||
let hy_alpn = Self::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
|
||||
|
||||
// meta 的一些处理
|
||||
let meta_guard_alpha = Self::to_script("verge_meta_guard", include_str!("./builtin/meta_guard.js"));
|
||||
|
||||
// meta 1.13.2 alpn string 转 数组
|
||||
let hy_alpn_alpha = Self::to_script("verge_hy_alpn", include_str!("./builtin/meta_hy_alpn.js"));
|
||||
|
||||
vec![
|
||||
(ChainSupport::ClashMeta, hy_alpn),
|
||||
(ChainSupport::ClashMeta, meta_guard),
|
||||
(ChainSupport::ClashMetaAlpha, hy_alpn_alpha),
|
||||
(ChainSupport::ClashMetaAlpha, meta_guard_alpha),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn to_script<U: Into<String>, D: Into<String>>(uid: U, data: D) -> Self {
|
||||
Self {
|
||||
uid: uid.into(),
|
||||
data: ChainType::Script(data.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainSupport {
|
||||
pub fn is_support(&self, core: Option<&String>) -> bool {
|
||||
match core {
|
||||
Some(core) => matches!(
|
||||
(self, core.as_str()),
|
||||
(Self::ClashMeta, "verge-mihomo") | (Self::ClashMetaAlpha, "verge-mihomo-alpha")
|
||||
),
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ fn lowercase_key(key: &str) -> Value {
|
||||
pub fn use_lowercase(config: &Mapping) -> Mapping {
|
||||
let mut lowercased = Mapping::new();
|
||||
|
||||
for (key, value) in config.iter() {
|
||||
for (key, value) in config.into_iter() {
|
||||
if let Some(key_str) = key.as_str() {
|
||||
lowercased.insert(lowercase_key(key_str), value.clone());
|
||||
}
|
||||
@@ -35,6 +35,17 @@ pub fn use_lowercase(config: &Mapping) -> Mapping {
|
||||
lowercased
|
||||
}
|
||||
|
||||
pub fn use_lowercase_owned(config: Mapping) -> Mapping {
|
||||
let mut lowercased = Mapping::new();
|
||||
|
||||
for (key, value) in config {
|
||||
if let Some(key_str) = key.as_str() {
|
||||
lowercased.insert(lowercase_key(key_str), value);
|
||||
}
|
||||
}
|
||||
lowercased
|
||||
}
|
||||
|
||||
pub fn use_sort(mut config: Mapping) -> Mapping {
|
||||
let mut sorted = Mapping::new();
|
||||
HANDLE_FIELDS.into_iter().for_each(|key| {
|
||||
|
||||
+127
-64
@@ -8,7 +8,7 @@ mod tun;
|
||||
|
||||
use self::{
|
||||
chain::{AsyncChainItemFrom as _, ChainItem, ChainType},
|
||||
field::{use_keys, use_lowercase, use_sort},
|
||||
field::{use_keys, use_lowercase_owned, use_sort},
|
||||
merge::use_merge,
|
||||
script::use_script,
|
||||
seq::{SeqMap, use_seq},
|
||||
@@ -34,6 +34,7 @@ type ResultLog = Vec<(String, String)>;
|
||||
#[derive(Debug)]
|
||||
struct ConfigValues {
|
||||
clash_config: Mapping,
|
||||
clash_core: Option<String>,
|
||||
enable_tun: bool,
|
||||
enable_builtin: bool,
|
||||
socks_enabled: bool,
|
||||
@@ -127,7 +128,8 @@ async fn get_config_values(profile_uid: &str) -> ConfigValues {
|
||||
let dns_settings = verge_arc.dns_settings_for(profile_uid);
|
||||
let dns_override_confirmation = dns_settings.confirmation;
|
||||
|
||||
let (enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = (
|
||||
let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = (
|
||||
Some(verge_arc.get_valid_clash_core()),
|
||||
enable_tun_mode.unwrap_or(false),
|
||||
enable_builtin_enhanced.unwrap_or(true),
|
||||
verge_socks_enabled.unwrap_or(false),
|
||||
@@ -146,6 +148,7 @@ async fn get_config_values(profile_uid: &str) -> ConfigValues {
|
||||
|
||||
ConfigValues {
|
||||
clash_config,
|
||||
clash_core,
|
||||
enable_tun,
|
||||
enable_builtin,
|
||||
socks_enabled,
|
||||
@@ -196,25 +199,35 @@ async fn collect_profile_items(profiles: &IProfiles) -> Result<ProfileItems> {
|
||||
|
||||
let name = current_item.name.clone().unwrap_or_default();
|
||||
|
||||
let ProfileItems {
|
||||
merge_item: default_merge,
|
||||
script_item: default_script,
|
||||
rules_item: default_rules,
|
||||
proxies_item: default_proxies,
|
||||
groups_item: default_groups,
|
||||
global_merge: default_global_merge,
|
||||
global_script: default_global_script,
|
||||
..
|
||||
} = ProfileItems::default();
|
||||
|
||||
let (merge_item, script_item, rules_item, proxies_item, groups_item, global_merge, global_script) = tokio::join!(
|
||||
chain_item_or_default(profiles.get_item(&merge_uid).ok(), || default_merge,),
|
||||
chain_item_or_default(profiles.get_item(&script_uid).ok(), || default_script,),
|
||||
chain_item_or_default(profiles.get_item(&rules_uid).ok(), || default_rules,),
|
||||
chain_item_or_default(profiles.get_item(&proxies_uid).ok(), || default_proxies,),
|
||||
chain_item_or_default(profiles.get_item(&groups_uid).ok(), || default_groups,),
|
||||
chain_item_or_default(profiles.get_item("Merge").ok(), || default_global_merge,),
|
||||
chain_item_or_default(profiles.get_item("Script").ok(), || default_global_script,),
|
||||
chain_item_or_default(profiles.get_item(&merge_uid).ok(), || ChainItem {
|
||||
uid: "".into(),
|
||||
data: ChainType::Merge(Mapping::new()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item(&script_uid).ok(), || ChainItem {
|
||||
uid: "".into(),
|
||||
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item(&rules_uid).ok(), || ChainItem {
|
||||
uid: "".into(),
|
||||
data: ChainType::Rules(SeqMap::default()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item(&proxies_uid).ok(), || ChainItem {
|
||||
uid: "".into(),
|
||||
data: ChainType::Proxies(SeqMap::default()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item(&groups_uid).ok(), || ChainItem {
|
||||
uid: "".into(),
|
||||
data: ChainType::Groups(SeqMap::default()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item("Merge").ok(), || ChainItem {
|
||||
uid: "Merge".into(),
|
||||
data: ChainType::Merge(Mapping::new()),
|
||||
},),
|
||||
chain_item_or_default(profiles.get_item("Script").ok(), || ChainItem {
|
||||
uid: "Script".into(),
|
||||
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
|
||||
},),
|
||||
);
|
||||
|
||||
Ok(ProfileItems {
|
||||
@@ -230,16 +243,16 @@ async fn collect_profile_items(profiles: &IProfiles) -> Result<ProfileItems> {
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_override_items(
|
||||
async fn process_global_items(
|
||||
mut config: Mapping,
|
||||
mut exists_keys: Vec<String>,
|
||||
mut result_map: HashMap<String, ResultLog>,
|
||||
merge_item: ChainItem,
|
||||
script_item: ChainItem,
|
||||
global_merge: ChainItem,
|
||||
global_script: ChainItem,
|
||||
profile_name: &String,
|
||||
authoritative: &AuthoritativeFields,
|
||||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
if let ChainType::Merge(merge) = merge_item.data {
|
||||
if let ChainType::Merge(merge) = global_merge.data {
|
||||
exists_keys.extend(use_keys(&merge));
|
||||
let before = authoritative.current(&config);
|
||||
config = use_merge(&merge, config);
|
||||
@@ -249,11 +262,11 @@ async fn process_override_items(
|
||||
.map(discarded_note)
|
||||
.collect();
|
||||
if !notes.is_empty() {
|
||||
result_map.entry(merge_item.uid).or_default().extend(notes);
|
||||
result_map.entry(global_merge.uid).or_default().extend(notes);
|
||||
}
|
||||
}
|
||||
|
||||
if let ChainType::Script(script) = script_item.data {
|
||||
if let ChainType::Script(script) = global_script.data {
|
||||
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);
|
||||
@@ -264,7 +277,7 @@ async fn process_override_items(
|
||||
.into_iter()
|
||||
.map(discarded_note),
|
||||
);
|
||||
result_map.insert(script_item.uid, logs);
|
||||
result_map.insert(global_script.uid, logs);
|
||||
}
|
||||
|
||||
(config, exists_keys, result_map)
|
||||
@@ -531,6 +544,46 @@ fn ensure_lan_bind_address(mut config: Mapping) -> Mapping {
|
||||
config
|
||||
}
|
||||
|
||||
async fn process_profile_items(
|
||||
mut config: Mapping,
|
||||
mut exists_keys: Vec<String>,
|
||||
mut result_map: HashMap<String, ResultLog>,
|
||||
merge_item: ChainItem,
|
||||
script_item: ChainItem,
|
||||
profile_name: &String,
|
||||
authoritative: &AuthoritativeFields,
|
||||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||||
if let ChainType::Merge(merge) = merge_item.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(merge_item.uid).or_default().extend(notes);
|
||||
}
|
||||
}
|
||||
|
||||
if let ChainType::Script(script) = script_item.data {
|
||||
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(script_item.uid, logs);
|
||||
}
|
||||
|
||||
(config, exists_keys, result_map)
|
||||
}
|
||||
|
||||
/// Merges the app's clash config over the profile; switches are parameters to keep it pure.
|
||||
fn merge_default_config(
|
||||
mut config: Mapping,
|
||||
@@ -602,12 +655,25 @@ fn merge_default_config(
|
||||
config
|
||||
}
|
||||
|
||||
fn apply_builtin_scripts(mut config: Mapping, enable_builtin: bool) -> Mapping {
|
||||
fn apply_builtin_scripts(mut config: Mapping, clash_core: Option<String>, enable_builtin: bool) -> Mapping {
|
||||
if enable_builtin {
|
||||
// The JS path saw a lowercased view; lowercase once, only when the builtin runs.
|
||||
config = use_lowercase(&config);
|
||||
logging!(debug, Type::Core, "run builtin script verge_hy_alpn");
|
||||
config = builtin_hy_alpn(config);
|
||||
let items: Vec<_> = ChainItem::builtin()
|
||||
.into_iter()
|
||||
.filter(|(s, _)| s.is_support(clash_core.as_ref()))
|
||||
.map(|(_, c)| c)
|
||||
.collect();
|
||||
if !items.is_empty() {
|
||||
// The JS path saw a lowercased view; lowercase once, only when a builtin runs.
|
||||
config = use_lowercase_owned(config);
|
||||
for item in items {
|
||||
logging!(debug, Type::Core, "run builtin script {}", item.uid);
|
||||
config = match item.uid.as_str() {
|
||||
"verge_hy_alpn" => builtin_hy_alpn(config),
|
||||
"verge_meta_guard" => builtin_meta_guard(config),
|
||||
_ => config,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
@@ -627,6 +693,14 @@ fn builtin_hy_alpn(mut config: Mapping) -> Mapping {
|
||||
config
|
||||
}
|
||||
|
||||
fn builtin_meta_guard(mut config: Mapping) -> Mapping {
|
||||
if config.get("mode").and_then(Value::as_str) == Some("script") {
|
||||
config.insert(Value::from("mode"), Value::from("rule"));
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
fn cleanup_proxy_groups(mut config: Mapping) -> Mapping {
|
||||
// built-in proxies docs: https://wiki.metacubex.one/config/proxies/built-in
|
||||
const BUILTIN_POLICIES: &[&str] = &["DIRECT", "REJECT", "REJECT-DROP", "PASS", "PASS-RULE"];
|
||||
@@ -805,6 +879,7 @@ pub async fn enhance(
|
||||
let cfg_vals = get_config_values(profile_uid).await;
|
||||
let ConfigValues {
|
||||
clash_config,
|
||||
clash_core,
|
||||
enable_tun,
|
||||
enable_builtin,
|
||||
socks_enabled,
|
||||
@@ -854,13 +929,13 @@ pub async fn enhance(
|
||||
tproxy_enabled,
|
||||
);
|
||||
|
||||
let config = apply_builtin_scripts(config, enable_builtin);
|
||||
let config = apply_builtin_scripts(config, clash_core, enable_builtin);
|
||||
let config = use_tun(config, enable_tun);
|
||||
let (config, dns_settings) = apply_dns_settings(config, enable_dns_settings).await;
|
||||
|
||||
let authoritative = AuthoritativeFields::capture(&config, &gui_tun_keys, dns_settings);
|
||||
|
||||
let (config, exists_keys, result_map) = process_override_items(
|
||||
let (config, exists_keys, result_map) = process_global_items(
|
||||
config,
|
||||
exists_keys,
|
||||
result_map,
|
||||
@@ -871,7 +946,7 @@ pub async fn enhance(
|
||||
)
|
||||
.await;
|
||||
|
||||
let (config, exists_keys, result_map) = process_override_items(
|
||||
let (config, exists_keys, result_map) = process_profile_items(
|
||||
config,
|
||||
exists_keys,
|
||||
result_map,
|
||||
@@ -1353,7 +1428,7 @@ mod authoritative_field_tests {
|
||||
mod tests {
|
||||
use super::{
|
||||
AuthoritativeFields, ChainItem, ChainType, Mapping, cleanup_proxy_groups, ensure_lan_bind_address,
|
||||
process_override_items, use_keys,
|
||||
process_global_items, process_profile_items, use_keys,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1401,28 +1476,22 @@ mod tests {
|
||||
data: ChainType::Merge(merge.clone()),
|
||||
};
|
||||
let profile_name = "test-profile".into();
|
||||
let (config, keys, logs) = process_override_items(
|
||||
let (config, keys, logs) = process_global_items(
|
||||
config,
|
||||
vec![],
|
||||
HashMap::new(),
|
||||
merge_item("global-merge"),
|
||||
ChainItem {
|
||||
uid: "global-script".into(),
|
||||
data: ChainType::Script(script.into()),
|
||||
},
|
||||
ChainItem::to_script("global-script", script),
|
||||
&profile_name,
|
||||
&authoritative,
|
||||
)
|
||||
.await;
|
||||
let (config, _, logs) = process_override_items(
|
||||
let (config, _, logs) = process_profile_items(
|
||||
config,
|
||||
keys,
|
||||
logs,
|
||||
merge_item("profile-merge"),
|
||||
ChainItem {
|
||||
uid: "profile-script".into(),
|
||||
data: ChainType::Script(script.into()),
|
||||
},
|
||||
ChainItem::to_script("profile-script", script),
|
||||
&profile_name,
|
||||
&authoritative,
|
||||
)
|
||||
@@ -1467,19 +1536,16 @@ mod tests {
|
||||
nested: {winner: global-merge}, dns: {enable: false}, tun: {enable: false}}",
|
||||
)),
|
||||
};
|
||||
let global_script = ChainItem {
|
||||
uid: "Script".into(),
|
||||
data: ChainType::Script(
|
||||
r#"function main(config) {
|
||||
let global_script = ChainItem::to_script(
|
||||
"Script",
|
||||
r#"function main(config) {
|
||||
config["global-script-wins"] = "global-script";
|
||||
config["profile-merge-wins"] = "global-script";
|
||||
config["profile-script-wins"] = "global-script";
|
||||
config.nested.winner = "global-script";
|
||||
return config;
|
||||
}"#
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
}"#,
|
||||
);
|
||||
let profile_merge = ChainItem {
|
||||
uid: "profile-merge".into(),
|
||||
data: ChainType::Merge(mapping(
|
||||
@@ -1487,21 +1553,18 @@ mod tests {
|
||||
nested: {winner: profile-merge}}",
|
||||
)),
|
||||
};
|
||||
let profile_script = ChainItem {
|
||||
uid: "profile-script".into(),
|
||||
data: ChainType::Script(
|
||||
r#"function main(config) {
|
||||
let profile_script = ChainItem::to_script(
|
||||
"profile-script",
|
||||
r#"function main(config) {
|
||||
config["profile-script-wins"] = "profile-script";
|
||||
config.nested.winner = "profile-script";
|
||||
return config;
|
||||
}"#
|
||||
.into(),
|
||||
),
|
||||
};
|
||||
}"#,
|
||||
);
|
||||
|
||||
let profile_name = "test-profile".into();
|
||||
let authoritative = AuthoritativeFields::capture(&config, &[], Mapping::new());
|
||||
let (config, exists_keys, result_map) = process_override_items(
|
||||
let (config, exists_keys, result_map) = process_global_items(
|
||||
config,
|
||||
exists_keys,
|
||||
HashMap::new(),
|
||||
@@ -1511,7 +1574,7 @@ mod tests {
|
||||
&authoritative,
|
||||
)
|
||||
.await;
|
||||
let (config, exists_keys, _) = process_override_items(
|
||||
let (config, exists_keys, _) = process_profile_items(
|
||||
config,
|
||||
exists_keys,
|
||||
result_map,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{process::AsyncHandler, utils::tmpl};
|
||||
|
||||
use super::field::use_lowercase;
|
||||
use super::field::{use_lowercase, use_lowercase_owned};
|
||||
use anyhow::{Error, Result};
|
||||
use boa_engine::{Context, JsString, JsValue, Source, js_string, native_function::NativeFunction, property::Attribute};
|
||||
use clash_verge_logging::{Type, logging};
|
||||
@@ -22,7 +22,7 @@ type EvalOutcome = (Result<Mapping, std::string::String>, Vec<(String, String)>)
|
||||
pub(super) async fn use_script(script: String, config: Mapping, name: String) -> ScriptResult {
|
||||
// The template is identity-plus-lowercasing; skip the Boa round-trip.
|
||||
if script == tmpl::ITEM_SCRIPT {
|
||||
return (use_lowercase(&config), vec![], vec![]);
|
||||
return (use_lowercase_owned(config), vec![], vec![]);
|
||||
}
|
||||
|
||||
let json = match serde_json::to_string(&use_lowercase(&config)) {
|
||||
@@ -173,7 +173,7 @@ fn eval_script(script: &str, config_json: &str, name: &String) -> EvalOutcome {
|
||||
}
|
||||
|
||||
match parse_json_safely(&result) {
|
||||
Ok(config) => (Ok(use_lowercase(&config)), outputs.lock().to_vec()),
|
||||
Ok(config) => (Ok(use_lowercase_owned(config)), outputs.lock().to_vec()),
|
||||
Err(err) => {
|
||||
logging!(
|
||||
error,
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("fake-ip");
|
||||
|
||||
if current_mode == "fake-ip" {
|
||||
if current_mode == "fake-ip" || !dns_val.contains_key(Value::from("enhanced-mode")) {
|
||||
revise!(dns_val, "enable", true);
|
||||
revise!(dns_val, "ipv6", ipv6_val);
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ async fn finalize_restored_verge_config(
|
||||
let restored_clash = IClashTemp::new().await;
|
||||
let clash_draft = Config::clash().await;
|
||||
clash_draft.edit_draft(|d| {
|
||||
*d = restored_clash;
|
||||
*d = restored_clash.clone();
|
||||
});
|
||||
clash_draft.apply();
|
||||
|
||||
let restored_profiles = IProfiles::new().await;
|
||||
let profiles_draft = Config::profiles().await;
|
||||
profiles_draft.edit_draft(|d| {
|
||||
*d = restored_profiles;
|
||||
*d = restored_profiles.clone();
|
||||
});
|
||||
profiles_draft.apply();
|
||||
|
||||
@@ -62,20 +62,20 @@ async fn finalize_restored_verge_config(
|
||||
*d = restored.clone();
|
||||
});
|
||||
verge_draft.apply();
|
||||
}
|
||||
|
||||
// Turn it off here; a failing core side effect below would otherwise skip this step.
|
||||
if !restored.enable_system_proxy.unwrap_or_default() {
|
||||
let result = async {
|
||||
let _lifecycle = CoreManager::global().lifecycle_lock.lock().await;
|
||||
proxy_control::apply().await?;
|
||||
proxy_control::refresh_guard().await
|
||||
}
|
||||
.await;
|
||||
if let Err(err) = result {
|
||||
logging!(error, Type::Backup, "Failed to turn the system proxy off: {err:#}");
|
||||
if SystemProxyStateUnknown::is_in(&err) {
|
||||
return Err(err);
|
||||
// Turn it off here; a failing core side effect below would otherwise skip this step.
|
||||
if !restored.enable_system_proxy.unwrap_or_default() {
|
||||
let result = async {
|
||||
let _lifecycle = CoreManager::global().lifecycle_lock.lock().await;
|
||||
proxy_control::apply().await?;
|
||||
proxy_control::refresh_guard().await
|
||||
}
|
||||
.await;
|
||||
if let Err(err) = result {
|
||||
logging!(error, Type::Backup, "Failed to turn the system proxy off: {err:#}");
|
||||
if SystemProxyStateUnknown::is_in(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,27 +134,14 @@ pub async fn delete_webdav_backup(filename: String) -> Result<()> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Backup creation strips the WebDAV credentials, so the live ones are kept.
|
||||
async fn restore_backup_archive(path: PathBuf) -> Result<()> {
|
||||
let (webdav_url, webdav_username, webdav_password) = {
|
||||
let verge = Config::verge().await;
|
||||
let verge = verge.latest_arc();
|
||||
(
|
||||
verge.webdav_url.clone(),
|
||||
verge.webdav_username.clone(),
|
||||
verge.webdav_password.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&path)).await??;
|
||||
let mut zip = zip::ZipArchive::new(file)?;
|
||||
let _profile_write = crate::config::profiles::PROFILE_WRITE_LOCK.lock().await;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
finalize_restored_verge_config(webdav_url, webdav_username, webdav_password).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(filename = %filename))]
|
||||
pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
let verge = Config::verge().await;
|
||||
let verge_data = verge.latest_arc();
|
||||
let webdav_url = verge_data.webdav_url.clone();
|
||||
let webdav_username = verge_data.webdav_username.clone();
|
||||
let webdav_password = verge_data.webdav_password.clone();
|
||||
|
||||
let backup_storage_path = app_home_dir()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to get app home dir: {e}"))?
|
||||
.join(filename.as_str());
|
||||
@@ -171,7 +158,12 @@ pub async fn restore_webdav_backup(filename: String) -> Result<()> {
|
||||
err
|
||||
})?;
|
||||
|
||||
let res = restore_backup_archive(backup_storage_path.clone()).await;
|
||||
let value = backup_storage_path.clone();
|
||||
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&value)).await??;
|
||||
let mut zip = zip::ZipArchive::new(file)?;
|
||||
let _profile_write = crate::config::profiles::PROFILE_WRITE_LOCK.lock().await;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
let res = finalize_restored_verge_config(webdav_url, webdav_username, webdav_password).await;
|
||||
let _ = backup_storage_path.remove_if_exists().await;
|
||||
res
|
||||
}
|
||||
@@ -340,7 +332,22 @@ pub async fn restore_local_backup(filename: String) -> Result<()> {
|
||||
return Err(anyhow!("Backup file not found: {}", filename));
|
||||
}
|
||||
|
||||
restore_backup_archive(target_path).await
|
||||
let (webdav_url, webdav_username, webdav_password) = {
|
||||
let verge = Config::verge().await;
|
||||
let verge = verge.latest_arc();
|
||||
(
|
||||
verge.webdav_url.clone(),
|
||||
verge.webdav_username.clone(),
|
||||
verge.webdav_password.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
let file = AsyncHandler::spawn_blocking(move || std::fs::File::open(&target_path)).await??;
|
||||
let mut zip = zip::ZipArchive::new(file)?;
|
||||
let _profile_write = crate::config::profiles::PROFILE_WRITE_LOCK.lock().await;
|
||||
zip.extract(app_home_dir()?)?;
|
||||
finalize_restored_verge_config(webdav_url, webdav_username, webdav_password).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, level = "info", fields(filename = %filename, destination = %destination))]
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::core::notify::{Refresh, announce};
|
||||
use crate::{
|
||||
config::{Config, MixedPort},
|
||||
core::{CoreManager, handle, tray},
|
||||
feat::run_exit_cleanup_flow,
|
||||
feat::clean_async,
|
||||
process::AsyncHandler,
|
||||
utils,
|
||||
};
|
||||
@@ -14,8 +14,15 @@ use smartstring::alias::String;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
static TLS_CONFIG: LazyLock<Arc<rustls::ClientConfig>> =
|
||||
LazyLock::new(|| Arc::new(utils::network::build_webpki_rooted_tls_config().expect("Failed to set TLS versions")));
|
||||
static TLS_CONFIG: LazyLock<Arc<rustls::ClientConfig>> = LazyLock::new(|| {
|
||||
let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let config = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
|
||||
.with_safe_default_protocol_versions()
|
||||
.expect("Failed to set TLS versions")
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
Arc::new(config)
|
||||
});
|
||||
|
||||
pub async fn restart_clash_core() {
|
||||
match CoreManager::global().restart_core().await {
|
||||
@@ -31,9 +38,38 @@ pub async fn restart_clash_core() {
|
||||
|
||||
pub async fn restart_app() {
|
||||
logging!(debug, Type::System, "启动重启应用流程");
|
||||
let cleanup_result = run_exit_cleanup_flow(NoticeStatus::AppRestartCoreStopFailed).await;
|
||||
handle::Handle::global().set_is_exiting();
|
||||
|
||||
Config::apply_all_and_save_file().await;
|
||||
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
"资源清理完成,退出代码: {}",
|
||||
if cleanup_result.all_success { 0 } else { 1 }
|
||||
);
|
||||
|
||||
if !cleanup_result.core_stopped {
|
||||
handle::Handle::global().clear_is_exiting();
|
||||
// A failed stop may still have marked the core stopped, and exit cleanup skipped the DNS restore.
|
||||
// A core that kept running keeps its DNS, whatever an unapplied config draft says.
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let manager = CoreManager::global();
|
||||
let _lifecycle = manager.lifecycle_lock.lock().await;
|
||||
if matches!(
|
||||
*manager.get_running_mode(),
|
||||
crate::core::manager::RunningMode::NotRunning
|
||||
) {
|
||||
crate::utils::resolve::dns::sync_public_dns().await;
|
||||
}
|
||||
}
|
||||
handle::Handle::notice(
|
||||
NoticeStatus::AppRestartCoreStopFailed,
|
||||
cleanup_result.stop_error.unwrap_or_default(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,26 @@ pub async fn switch_proxy_node(group_name: &str, proxy_name: &str) {
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
announce(Refresh::Proxies);
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Tray, "切换代理失败: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
match handle::Handle::mihomo()
|
||||
.select_node_for_group(group_name, proxy_name)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Tray, "代理切换回退成功: {} -> {}", group_name, proxy_name);
|
||||
record_switched_node(group_name, proxy_name).await;
|
||||
let _ = tray::Tray::global().update_menu().await;
|
||||
}
|
||||
Err(err) => {
|
||||
logging!(error, Type::Tray, "代理切换最终失败: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn should_update_profile(uid: &String, ignore_auto_update: bool) -> Result<Option<(String, Option<PrfOption>)>> {
|
||||
|
||||
@@ -130,7 +130,9 @@ pub async fn open_or_close_dashboard() {
|
||||
logging!(info, Type::Window, "Window toggle result: {result:?}");
|
||||
}
|
||||
|
||||
pub(crate) async fn run_exit_cleanup_flow(stop_failed_notice: NoticeStatus) -> CleanupResult {
|
||||
pub async fn quit() {
|
||||
logging!(debug, Type::System, "启动退出流程");
|
||||
// 设置退出标志
|
||||
handle::Handle::global().set_is_exiting();
|
||||
|
||||
Config::apply_all_and_save_file().await;
|
||||
@@ -160,18 +162,11 @@ pub(crate) async fn run_exit_cleanup_flow(stop_failed_notice: NoticeStatus) -> C
|
||||
}
|
||||
}
|
||||
handle::Handle::notice(
|
||||
stop_failed_notice,
|
||||
cleanup_result.stop_error.clone().unwrap_or_default(),
|
||||
NoticeStatus::AppQuitCoreStopFailed,
|
||||
cleanup_result.stop_error.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
|
||||
cleanup_result
|
||||
}
|
||||
|
||||
pub async fn quit() {
|
||||
logging!(debug, Type::System, "启动退出流程");
|
||||
let cleanup_result = run_exit_cleanup_flow(NoticeStatus::AppQuitCoreStopFailed).await;
|
||||
|
||||
utils::server::shutdown_embedded_server();
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
app_handle.exit(if cleanup_result.all_success { 0 } else { 1 });
|
||||
|
||||
@@ -354,6 +354,7 @@ pub(super) async fn init_dns_config() -> Result<()> {
|
||||
Value::String("*.arpa".into()),
|
||||
Value::String("time.*.com".into()),
|
||||
Value::String("ntp.*.com".into()),
|
||||
Value::String("time.*.com".into()),
|
||||
Value::String("+.market.xiaomi.com".into()),
|
||||
Value::String("localhost.ptlogin2.qq.com".into()),
|
||||
Value::String("*.msftncsi.com".into()),
|
||||
@@ -508,9 +509,11 @@ pub async fn init_resources() -> Result<()> {
|
||||
let app_dir = dirs::app_home_dir()?;
|
||||
let res_dir = dirs::app_resources_dir()?;
|
||||
|
||||
// app_home is created by `ensure_directories` in `init_config`, which always runs first.
|
||||
if !app_dir.exists() {
|
||||
std::mem::drop(fs::create_dir_all(&app_dir).await);
|
||||
}
|
||||
if !res_dir.exists() {
|
||||
fs::create_dir_all(&res_dir).await?;
|
||||
std::mem::drop(fs::create_dir_all(&res_dir).await);
|
||||
}
|
||||
|
||||
let file_list = ["Country.mmdb", "ASN.mmdb", "geoip.dat", "geosite.dat"];
|
||||
|
||||
@@ -48,17 +48,6 @@ enum TlsRootMode {
|
||||
StaticWebpkiRoots,
|
||||
}
|
||||
|
||||
/// ALPN stays unset: raw TLS connectors offer none, while reqwest needs the h2 offer.
|
||||
pub(crate) fn build_webpki_rooted_tls_config() -> Result<rustls::ClientConfig> {
|
||||
let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let config = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
|
||||
.with_safe_default_protocol_versions()?
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub struct NetworkManager;
|
||||
|
||||
impl NetworkManager {
|
||||
@@ -113,7 +102,13 @@ impl NetworkManager {
|
||||
}
|
||||
|
||||
fn build_static_webpki_tls_config() -> Result<rustls::ClientConfig> {
|
||||
let mut config = build_webpki_rooted_tls_config()?;
|
||||
let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
let mut config =
|
||||
rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
|
||||
.with_safe_default_protocol_versions()?
|
||||
.with_root_certificates(root_store)
|
||||
.with_no_client_auth();
|
||||
|
||||
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
|
||||
Ok(config)
|
||||
@@ -160,23 +155,6 @@ impl NetworkManager {
|
||||
detail.contains("protocolversion") || detail.contains("protocol version")
|
||||
}
|
||||
|
||||
/// `allow_fallback` stays false when the caller already disabled certificate verification.
|
||||
async fn request_with_static_webpki_fallback<T, F, Fut>(allow_fallback: bool, mut request: F) -> Result<T>
|
||||
where
|
||||
F: FnMut(TlsRootMode) -> Fut,
|
||||
Fut: std::future::Future<Output = Result<T>>,
|
||||
{
|
||||
match request(TlsRootMode::PlatformVerifier).await {
|
||||
Ok(value) => Ok(value),
|
||||
Err(err) if allow_fallback && Self::should_retry_with_static_webpki_roots(&err) => {
|
||||
request(TlsRootMode::StaticWebpkiRoots).await.map_err(|fallback_err| {
|
||||
fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed")
|
||||
})
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_with_tls_mode(
|
||||
&self,
|
||||
url: &str,
|
||||
@@ -286,21 +264,34 @@ impl NetworkManager {
|
||||
user_agent: Option<String>,
|
||||
accept_invalid_certs: bool,
|
||||
) -> Result<HttpResponse> {
|
||||
Self::request_with_static_webpki_fallback(!accept_invalid_certs, |tls_root_mode| {
|
||||
let user_agent = user_agent.clone();
|
||||
async move {
|
||||
self.get_with_tls_mode(
|
||||
let platform_result = self
|
||||
.get_with_tls_mode(
|
||||
url,
|
||||
proxy_type,
|
||||
timeout_secs,
|
||||
user_agent.clone(),
|
||||
accept_invalid_certs,
|
||||
TlsRootMode::PlatformVerifier,
|
||||
)
|
||||
.await;
|
||||
|
||||
match platform_result {
|
||||
Ok(response) => Ok(response),
|
||||
Err(err) if !accept_invalid_certs && Self::should_retry_with_static_webpki_roots(&err) => self
|
||||
.get_with_tls_mode(
|
||||
url,
|
||||
proxy_type,
|
||||
timeout_secs,
|
||||
user_agent,
|
||||
accept_invalid_certs,
|
||||
tls_root_mode,
|
||||
TlsRootMode::StaticWebpkiRoots,
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await
|
||||
.map_err(|fallback_err| {
|
||||
fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed")
|
||||
}),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_bytes_with_tls_mode(
|
||||
@@ -349,10 +340,19 @@ impl NetworkManager {
|
||||
timeout_secs: Option<u64>,
|
||||
max_bytes: usize,
|
||||
) -> Result<Vec<u8>> {
|
||||
Self::request_with_static_webpki_fallback(true, |tls_root_mode| async move {
|
||||
self.get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, tls_root_mode)
|
||||
let platform_result = self
|
||||
.get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, TlsRootMode::PlatformVerifier)
|
||||
.await;
|
||||
|
||||
match platform_result {
|
||||
Ok(body) => Ok(body),
|
||||
Err(err) if Self::should_retry_with_static_webpki_roots(&err) => self
|
||||
.get_bytes_with_tls_mode(url, proxy_type, timeout_secs, max_bytes, TlsRootMode::StaticWebpkiRoots)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.map_err(|fallback_err| {
|
||||
fallback_err.context("static webpki roots fallback failed after platform TLS verifier failed")
|
||||
}),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::core::notify::NoticeStatus;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{
|
||||
@@ -95,6 +97,18 @@ async fn resolve_setup() {
|
||||
resolve_done();
|
||||
}
|
||||
|
||||
pub async fn resolve_reset_async() -> Result<(), anyhow::Error> {
|
||||
CoreManager::global().stop_core().await?;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use dns::restore_public_dns;
|
||||
restore_public_dns().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_scheme() {
|
||||
logging_error!(Type::Setup, init::init_scheme());
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ export type IVerge_Deserialize = {
|
||||
start_page?: string | null
|
||||
startup_script?: string | null
|
||||
traffic_graph?: boolean | null
|
||||
/** show memory info (only for Mihomo) */
|
||||
/** show memory info (only for Clash Meta) */
|
||||
enable_memory_usage?: boolean | null
|
||||
enable_group_icon?: boolean | null
|
||||
pause_render_traffic_stats_on_blur?: boolean | null
|
||||
@@ -455,7 +455,7 @@ export type IVerge_Serialize = {
|
||||
start_page: string | null
|
||||
startup_script: string | null
|
||||
traffic_graph: boolean | null
|
||||
/** show memory info (only for Mihomo) */
|
||||
/** show memory info (only for Clash Meta) */
|
||||
enable_memory_usage: boolean | null
|
||||
enable_group_icon?: boolean | null
|
||||
pause_render_traffic_stats_on_blur: boolean | null
|
||||
|
||||
Reference in New Issue
Block a user