Compare commits

..

28 Commits

49 changed files with 1045 additions and 509 deletions

View File

@@ -92,7 +92,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
VITE_WIN_PORTABLE: 1
release-for-linux:
strategy:

View File

@@ -12,7 +12,7 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
## Features
- Since the clash core has been removed. The project no longer maintains the clash core, but only the Clash Meta core.
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://github.com/clash-verge-rev/clash-verge-rev/wiki/%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
- Profiles management and enhancement (by yaml and Javascript). [Doc](https://clash-verge-rev.github.io)
- Simple UI and supports custom theme color.
- Built-in support [Clash.Meta(mihomo)](https://github.com/MetaCubeX/mihomo) core.
- System proxy setting and guard.
@@ -41,18 +41,18 @@ A Clash Meta GUI based on <a href="https://github.com/tauri-apps/tauri">Tauri</a
Download from [release](https://github.com/clash-verge-rev/clash-verge-rev/releases). Supports Windows (x64/x86), Linux (x64/arm64) and macOS 10.15+ (intel/apple).
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/Clash.Verge_1.4.4_x64-setup.exe)
- [Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/Clash.Verge_1.4.4_x86-setup.exe)
- [Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/Clash.Verge_1.4.4_arm64-setup.exe)
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/Clash.Verge_1.4.6_x64-setup.exe)
- [Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/Clash.Verge_1.4.6_x86-setup.exe)
- [Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/Clash.Verge_1.4.6_arm64-setup.exe)
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/Clash.Verge_1.4.4_x64.dmg)
- [macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/Clash.Verge_1.4.4_aarch64.dmg)
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/Clash.Verge_1.4.6_x64.dmg)
- [macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/Clash.Verge_1.4.6_aarch64.dmg)
- [Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/clash-verge_1.4.4_amd64.AppImage)
- [Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/clash-verge_1.4.4_amd64.deb)
- [Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/clash-verge_1.4.4_i386.AppImage)
- [Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/clash-verge_1.4.4_i386.deb)
- [Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.4/clash-verge_1.4.4_arm64.deb)
- [Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/clash-verge_1.4.6_amd64.AppImage)
- [Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/clash-verge_1.4.6_amd64.deb)
- [Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/clash-verge_1.4.6_i386.AppImage)
- [Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/clash-verge_1.4.6_i386.deb)
- [Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.6/clash-verge_1.4.6_arm64.deb)
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
@@ -112,11 +112,10 @@ Issue and PR welcome!
Clash Verge rev was based on or inspired by these projects and so on:
- [keiko233/clash-nyanpasu](https://github.com/keiko233/clash-nyanpasu): A Clash Verge variant.
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): A Clash GUI based on tauri. Supports Windows, macOS and Linux.
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop applications with a web frontend.
- [Dreamacro/clash](https://github.com/Dreamacro/clash): A rule-based tunnel in Go.
- [MetaCubeX/Clash.Meta](https://github.com/MetaCubeX/Clash.Meta): A rule-based tunnel in Go.
- [MetaCubeX/mihomo](https://github.com/MetaCubeX/mihomo): A rule-based tunnel in Go.
- [Fndroid/clash_for_windows_pkg](https://github.com/Fndroid/clash_for_windows_pkg): A Windows/macOS GUI based on Clash.
- [vitejs/vite](https://github.com/vitejs/vite): Next generation frontend tooling. It's fast!

View File

@@ -1,3 +1,37 @@
## v1.4.6
### Features
- 更新 Clash Meta(mihomo) 内核到 v1.18.0
- 支持 URL Scheme(暂时仅支持 Windows)
- 添加窗口置顶按钮
- UI 优化调整
### Bugs Fixes
- 修复一些编译错误
- 获取订阅名称错误
- 订阅信息解析错误
---
## v1.4.5
### Features
- 更新 MacOS 托盘图标样式(@gxx2778 贡献)
### Bugs Fixes
- Windows 下更新时无法覆盖`clash-verge-service.exe`的问题(需要卸载重装一次服务,下次更新生效)
- 窗口最大化按钮变化问题
- 窗口尺寸保存错误问题
- 复制环境变量类型无法切换问题
- 某些情况下闪退的问题
- 某些订阅无法导入的问题
---
## v1.4.4
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "clash-verge",
"version": "1.4.4",
"version": "1.4.6",
"license": "GPL-3.0",
"scripts": {
"dev": "tauri dev",

View File

@@ -28,13 +28,13 @@ index 4c6dde5..5fd9ad8 100644
@@ -25,7 +25,6 @@ log4rs = "1"
nanoid = "0.4"
chrono = "0.4"
sysinfo = "0.29"
sysinfo = "0.30"
-rquickjs = "0.3" # 高版本不支持 Linux aarch64
serde_json = "1.0"
serde_yaml = "0.9"
auto-launch = "0.5"
@@ -34,6 +33,7 @@ port_scanner = "0.1.5"
delay_timer = "0.11"
delay_timer = "0.11.5"
parking_lot = "0.12"
percent-encoding = "2.3.1"
+quick-rs = { path = "quick-rs" }

View File

@@ -48,7 +48,7 @@ const SIDECAR_HOST = target
.match(/(?<=host: ).+(?=\s*)/g)[0];
/* ======= clash meta alpha======= */
const VERSION_URL =
const META_ALPHA_VERSION_URL =
"https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt";
const META_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`;
let META_ALPHA_VERSION;
@@ -57,7 +57,52 @@ const META_ALPHA_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-x64": "mihomo-darwin-amd64-cgo",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
"linux-arm64": "mihomo-linux-arm64",
"linux-arm": "mihomo-linux-armv7",
};
// Fetch the latest alpha release version from the version.txt file
async function getLatestAlphaVersion() {
const options = {};
const httpProxy =
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.HTTPS_PROXY ||
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
}
try {
const response = await fetch(META_ALPHA_VERSION_URL, {
...options,
method: "GET",
});
let v = await response.text();
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest alpha version: ${META_ALPHA_VERSION}`);
} catch (error) {
console.error("Error fetching latest alpha version:", error.message);
process.exit(1);
}
}
/* ======= clash meta stable ======= */
const META_VERSION_URL =
"https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt";
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
let META_VERSION;
const META_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64-cgo",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
@@ -66,34 +111,32 @@ const META_ALPHA_MAP = {
};
// Fetch the latest release version from the version.txt file
async function getLatestVersion() {
async function getLatestReleaseVersion() {
const options = {};
const httpProxy =
process.env.HTTP_PROXY ||
process.env.http_proxy ||
process.env.HTTPS_PROXY ||
process.env.https_proxy;
if (httpProxy) {
options.agent = proxyAgent(httpProxy);
}
try {
const response = await fetch(VERSION_URL, { method: "GET" });
const response = await fetch(META_VERSION_URL, {
...options,
method: "GET",
});
let v = await response.text();
META_ALPHA_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest release version: ${META_ALPHA_VERSION}`);
META_VERSION = v.trim(); // Trim to remove extra whitespaces
console.log(`Latest release version: ${META_VERSION}`);
} catch (error) {
console.error("Error fetching latest release version:", error.message);
process.exit(1);
}
}
/* ======= clash meta stable ======= */
const META_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`;
let META_VERSION = "v1.17.0";
const META_MAP = {
"win32-x64": "mihomo-windows-amd64-compatible",
"win32-ia32": "mihomo-windows-386",
"win32-arm64": "mihomo-windows-arm64",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
"linux-arm64": "mihomo-linux-arm64",
"linux-arm": "mihomo-linux-armv7",
};
/*
* check available
*/
@@ -273,8 +316,8 @@ async function downloadFile(url, path) {
/**
* main
*/
const SERVICE_URL =
"https://github.com/zzzgydi/clash-verge-service/releases/download/latest";
const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`;
const resolveService = () =>
resolveResource({
@@ -316,15 +359,16 @@ const tasks = [
// { name: "clash", func: resolveClash, retry: 5 },
{
name: "clash-meta-alpha",
func: () => getLatestVersion().then(() => resolveSidecar(clashMetaAlpha())),
func: () =>
getLatestAlphaVersion().then(() => resolveSidecar(clashMetaAlpha())),
retry: 5,
},
{
name: "clash-meta",
func: () => resolveSidecar(clashMeta()),
func: () =>
getLatestReleaseVersion().then(() => resolveSidecar(clashMeta())),
retry: 5,
},
// { name: "wintun", func: resolveWintun, retry: 5, winOnly: true },
{ name: "service", func: resolveService, retry: 5, winOnly: true },
{ name: "install", func: resolveInstall, retry: 5, winOnly: true },
{ name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true },

362
src-tauri/Cargo.lock generated
View File

@@ -101,6 +101,16 @@ version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "async-broadcast"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
dependencies = [
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-channel"
version = "1.9.0"
@@ -238,6 +248,17 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "async-recursion"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "async-signal"
version = "0.2.5"
@@ -559,7 +580,7 @@ dependencies = [
[[package]]
name = "clash-verge"
version = "1.4.4"
version = "1.4.6"
dependencies = [
"anyhow",
"auto-launch",
@@ -591,7 +612,6 @@ dependencies = [
"warp",
"which 5.0.0",
"window-shadows",
"windows-sys 0.52.0",
"winreg 0.52.0",
]
@@ -931,9 +951,9 @@ dependencies = [
[[package]]
name = "delay_timer"
version = "0.11.4"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e3040b73d9397711697558109c983a2dc6fc63e98785ffbefd3ece57b46b67"
checksum = "d70c0d5d2addc05d1e0cf7e60b3a5bf08415f03136a773d7c66b4e4a3b892cef"
dependencies = [
"anyhow",
"async-trait",
@@ -1136,6 +1156,27 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enumflags2"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939"
dependencies = [
"enumflags2_derive",
"serde",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@@ -1954,6 +1995,20 @@ dependencies = [
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http",
"hyper",
"rustls",
"tokio",
"tokio-rustls",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
@@ -2438,6 +2493,19 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "mac-notification-sys"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64"
dependencies = [
"cc",
"dirs-next",
"objc-foundation",
"objc_id",
"time",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
@@ -2511,6 +2579,15 @@ dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "memoffset"
version = "0.9.0"
@@ -2661,6 +2738,18 @@ dependencies = [
"memoffset 0.6.5",
]
[[package]]
name = "nix"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
"memoffset 0.7.1",
]
[[package]]
name = "nix"
version = "0.27.1"
@@ -2698,6 +2787,19 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "notify-rust"
version = "4.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226"
dependencies = [
"log 0.4.20",
"mac-notification-sys",
"serde",
"tauri-winrt-notification",
"zbus",
]
[[package]]
name = "ntapi"
version = "0.4.1"
@@ -2929,6 +3031,16 @@ dependencies = [
"num-traits",
]
[[package]]
name = "ordered-stream"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "os_pipe"
version = "1.1.4"
@@ -3240,7 +3352,7 @@ dependencies = [
"base64 0.21.5",
"indexmap 2.1.0",
"line-wrap",
"quick-xml",
"quick-xml 0.31.0",
"serde",
"time",
]
@@ -3367,6 +3479,15 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
@@ -3602,6 +3723,7 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
@@ -3611,12 +3733,15 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower-service",
"url",
@@ -3624,6 +3749,7 @@ dependencies = [
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"webpki-roots",
"winreg 0.50.0",
]
@@ -3651,6 +3777,20 @@ dependencies = [
"windows 0.37.0",
]
[[package]]
name = "ring"
version = "0.17.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74"
dependencies = [
"cc",
"getrandom 0.2.11",
"libc",
"spin",
"untrusted",
"windows-sys 0.48.0",
]
[[package]]
name = "rquickjs"
version = "0.3.1"
@@ -3745,6 +3885,18 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.21.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
dependencies = [
"log 0.4.20",
"ring",
"rustls-webpki",
"sct",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
@@ -3754,6 +3906,16 @@ dependencies = [
"base64 0.21.5",
]
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.14"
@@ -3802,6 +3964,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.9.2"
@@ -4242,6 +4414,12 @@ dependencies = [
"loom",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
@@ -4298,9 +4476,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.29.11"
version = "0.30.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd727fc423c2060f6c92d9534cef765c65a6ed3f428a03d7def74a8c4348e666"
checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2"
dependencies = [
"cfg-if",
"core-foundation-sys",
@@ -4308,7 +4486,7 @@ dependencies = [
"ntapi",
"once_cell",
"rayon",
"winapi",
"windows 0.52.0",
]
[[package]]
@@ -4470,6 +4648,7 @@ dependencies = [
"ignore",
"infer 0.9.0",
"minisign-verify",
"notify-rust",
"objc",
"once_cell",
"open 3.2.0",
@@ -4645,6 +4824,16 @@ dependencies = [
"toml 0.7.8",
]
[[package]]
name = "tauri-winrt-notification"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2"
dependencies = [
"quick-xml 0.30.0",
"windows 0.51.1",
]
[[package]]
name = "tempfile"
version = "3.8.1"
@@ -4862,6 +5051,16 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.14"
@@ -5096,6 +5295,17 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd2fc5d32b590614af8b0a20d837f32eca055edd0bbead59a9cfe80858be003"
[[package]]
name = "uds_windows"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
dependencies = [
"memoffset 0.9.0",
"tempfile",
"winapi",
]
[[package]]
name = "unicase"
version = "2.7.0"
@@ -5147,6 +5357,12 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.0"
@@ -5449,6 +5665,12 @@ dependencies = [
"system-deps 6.2.0",
]
[[package]]
name = "webpki-roots"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
[[package]]
name = "webview2-com"
version = "0.19.1"
@@ -5591,6 +5813,16 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
dependencies = [
"windows-core 0.51.1",
"windows-targets 0.48.5",
]
[[package]]
name = "windows"
version = "0.52.0"
@@ -6025,6 +6257,16 @@ dependencies = [
"libc",
]
[[package]]
name = "xdg-home"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd"
dependencies = [
"nix 0.26.4",
"winapi",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
@@ -6034,6 +6276,72 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "zbus"
version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948"
dependencies = [
"async-broadcast",
"async-executor",
"async-fs",
"async-io 1.13.0",
"async-lock 2.8.0",
"async-process",
"async-recursion",
"async-task",
"async-trait",
"blocking",
"byteorder",
"derivative",
"enumflags2",
"event-listener 2.5.3",
"futures-core",
"futures-sink",
"futures-util",
"hex",
"nix 0.26.4",
"once_cell",
"ordered-stream",
"rand 0.8.5",
"serde",
"serde_repr",
"sha1",
"static_assertions",
"tracing",
"uds_windows",
"winapi",
"xdg-home",
"zbus_macros",
"zbus_names",
"zvariant",
]
[[package]]
name = "zbus_macros"
version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"regex 1.10.2",
"syn 1.0.109",
"zvariant_utils",
]
[[package]]
name = "zbus_names"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9"
dependencies = [
"serde",
"static_assertions",
"zvariant",
]
[[package]]
name = "zerocopy"
version = "0.7.28"
@@ -6064,3 +6372,41 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
]
[[package]]
name = "zvariant"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c"
dependencies = [
"byteorder",
"enumflags2",
"libc",
"serde",
"static_assertions",
"zvariant_derive",
]
[[package]]
name = "zvariant_derive"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 1.0.109",
"zvariant_utils",
]
[[package]]
name = "zvariant_utils"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.4.4"
version = "1.4.6"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0"
@@ -24,28 +24,27 @@ dunce = "1.0"
log4rs = "1"
nanoid = "0.4"
chrono = "0.4"
sysinfo = "0.29"
sysinfo = "0.30"
rquickjs = "0.3" # 高版本不支持 Linux aarch64
serde_json = "1.0"
serde_yaml = "0.9"
auto-launch = "0.5"
once_cell = "1.18"
port_scanner = "0.1.5"
delay_timer = "0.11"
delay_timer = "0.11.5"
parking_lot = "0.12"
percent-encoding = "2.3.1"
window-shadows = { version = "0.2" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.11", features = ["json"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
tauri = { version = "1.5", features = ["icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
tauri = { version = "1.5", features = [ "notification-all", "icon-png", "clipboard-all", "global-shortcut-all", "process-all", "shell-all", "system-tray", "updater", "window-all"] }
[target.'cfg(windows)'.dependencies]
runas = "=1.0.0" # 高版本会返回错误 Status
deelevate = "0.2.0"
winreg = { version = "0.52", features = ["transactions"] }
windows-sys = { version = "0.52", features = ["Win32_System_LibraryLoader", "Win32_System_SystemInformation"] }
winreg = "0.52.0"
[target.'cfg(target_os = "linux")'.dependencies]
#openssl

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -256,6 +256,11 @@ pub async fn clash_api_get_proxy_delay(
}
}
#[tauri::command]
pub fn get_portable_flag() -> CmdResult<bool> {
Ok(*dirs::PORTABLE_FLAG.get().unwrap_or(&false))
}
#[cfg(windows)]
pub mod service {
use super::*;

View File

@@ -1,4 +1,4 @@
use crate::utils::{dirs, help, tmpl};
use crate::utils::{dirs, help, resolve::VERSION, tmpl};
use anyhow::{bail, Context, Result};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
@@ -96,7 +96,7 @@ impl PrfOption {
a.update_interval = b.update_interval.or(a.update_interval);
Some(a)
}
t @ _ => t.0.or(t.1),
t => t.0.or(t.1),
}
}
}
@@ -152,7 +152,7 @@ impl PrfItem {
let desc = item.desc.unwrap_or("".into());
PrfItem::from_script(name, desc)
}
typ @ _ => bail!("invalid profile item type \"{typ}\""),
typ => bail!("invalid profile item type \"{typ}\""),
}
}
@@ -190,7 +190,7 @@ impl PrfItem {
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false));
let user_agent = opt_ref.map_or(None, |o| o.user_agent.clone());
let mut builder = reqwest::ClientBuilder::new().no_proxy();
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
// 使用软件自己的代理
if self_proxy {
@@ -231,8 +231,11 @@ impl PrfItem {
};
}
let version = unsafe { dirs::APP_VERSION };
let version = format!("clash-verge/{version}");
let version = match VERSION.get() {
Some(v) => format!("clash-verge/v{}", v),
None => format!("clash-verge/unknown"),
};
builder = builder.user_agent(user_agent.unwrap_or(version));
let resp = builder.build()?.get(url).send().await?;
@@ -248,12 +251,11 @@ impl PrfItem {
let extra = match header.get("Subscription-Userinfo") {
Some(value) => {
let sub_info = value.to_str().unwrap_or("");
Some(PrfExtra {
upload: help::parse_str(sub_info, "upload=").unwrap_or(0),
download: help::parse_str(sub_info, "download=").unwrap_or(0),
total: help::parse_str(sub_info, "total=").unwrap_or(0),
expire: help::parse_str(sub_info, "expire=").unwrap_or(0),
upload: help::parse_str(sub_info, "upload").unwrap_or(0),
download: help::parse_str(sub_info, "download").unwrap_or(0),
total: help::parse_str(sub_info, "total").unwrap_or(0),
expire: help::parse_str(sub_info, "expire").unwrap_or(0),
})
}
None => None,
@@ -262,14 +264,18 @@ impl PrfItem {
// parse the Content-Disposition
let filename = match header.get("Content-Disposition") {
Some(value) => {
let filename = value.to_str().unwrap_or("");
match help::parse_str::<String>(filename, "filename=") {
Some(filename) => Some(filename),
None => match help::parse_str::<String>(filename, "filename*=") {
let filename = format!("{value:?}");
let filename = filename.trim_matches('"');
match help::parse_str::<String>(filename, "filename*") {
Some(filename) => {
let iter = percent_encoding::percent_decode(filename.as_bytes());
let filename = iter.decode_utf8().unwrap_or_default();
filename.split("''").last().map(|s| s.to_string())
}
None => match help::parse_str::<String>(filename, "filename") {
Some(filename) => {
let iter = percent_encoding::percent_decode(filename.as_bytes());
let filename = iter.decode_utf8().unwrap_or_default();
filename.split("''").last().map(|s| s.to_string())
let filename = filename.trim_matches('"');
Some(filename.to_string())
}
None => None,
},

View File

@@ -55,7 +55,12 @@ impl IProfiles {
pub fn template() -> Self {
Self {
valid: Some(vec!["dns".into()]),
valid: Some(vec![
"dns".into(),
"sub-rules".into(),
"unified-delay".into(),
"tcp-concurrent".into(),
]),
items: Some(vec![]),
..Self::default()
}

View File

@@ -152,7 +152,7 @@ impl IVerge {
proxy_guard_duration: Some(30),
auto_close_connection: Some(true),
enable_builtin_enhanced: Some(true),
enable_clash_fields: Some(false),
enable_clash_fields: Some(true),
auto_log_clean: Some(3),
..Self::default()
}

View File

@@ -20,7 +20,7 @@ pub async fn put_configs(path: &str) -> Result<()> {
match response.status().as_u16() {
204 => Ok(()),
status @ _ => {
status => {
bail!("failed to put configs with status \"{status}\"")
}
}

View File

@@ -5,7 +5,7 @@ use anyhow::{bail, Context, Result};
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use std::{fs, io::Write, sync::Arc, time::Duration};
use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
use sysinfo::{Pid, System};
use tauri::api::process::{Command, CommandChild, CommandEvent};
use tokio::time::sleep;

View File

@@ -1,5 +1,3 @@
use std::borrow::Cow;
/// 给clash内核的tun模式授权
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn grant_permission(core: String) -> anyhow::Result<()> {
@@ -13,9 +11,6 @@ pub fn grant_permission(core: String) -> anyhow::Result<()> {
#[cfg(target_os = "macos")]
let output = {
// the path of clash /Applications/Clash Verge.app/Contents/MacOS/clash
// https://apple.stackexchange.com/questions/82967/problem-with-empty-spaces-when-executing-shell-commands-in-applescript
// let path = escape(&path);
let path = path.replace(' ', "\\\\ ");
let shell = format!("chown root:admin {path}\nchmod +sx {path}");
let command = format!(r#"do shell script "{shell}" with administrator privileges"#);
@@ -50,33 +45,3 @@ pub fn grant_permission(core: String) -> anyhow::Result<()> {
anyhow::bail!("{stderr}");
}
}
#[allow(unused)]
pub fn escape<'a>(text: &'a str) -> Cow<'a, str> {
let bytes = text.as_bytes();
let mut owned = None;
for pos in 0..bytes.len() {
let special = match bytes[pos] {
b' ' => Some(b' '),
_ => None,
};
if let Some(s) = special {
if owned.is_none() {
owned = Some(bytes[0..pos].to_owned());
}
owned.as_mut().unwrap().push(b'\\');
owned.as_mut().unwrap().push(b'\\');
owned.as_mut().unwrap().push(s);
} else if let Some(owned) = owned.as_mut() {
owned.push(bytes[pos]);
}
}
if let Some(owned) = owned {
unsafe { Cow::Owned(String::from_utf8_unchecked(owned)) }
} else {
unsafe { Cow::Borrowed(std::str::from_utf8_unchecked(bytes)) }
}
}

View File

@@ -140,7 +140,7 @@ impl Tray {
#[cfg(not(target_os = "macos"))]
let icon = include_bytes!("../../icons/tray-icon.png").to_vec();
#[cfg(target_os = "macos")]
let icon = include_bytes!("../../icons/icon.png").to_vec();
let icon = include_bytes!("../../icons/mac-tray-icon.png").to_vec();
icon
};

View File

@@ -7,8 +7,8 @@ use runas::Command as RunasCommand;
use std::process::Command as StdCommand;
pub async fn invoke_uwptools() -> Result<()> {
let binary_path = dirs::service_path()?;
let tool_path = binary_path.with_file_name("enableLoopback.exe");
let resource_dir = dirs::app_resources_dir()?;
let tool_path = resource_dir.join("enableLoopback.exe");
if !tool_path.exists() {
bail!("enableLoopback exe not found");
@@ -17,10 +17,9 @@ pub async fn invoke_uwptools() -> Result<()> {
let token = Token::with_current_process()?;
let level = token.privilege_level()?;
match level {
match level {
PrivilegeLevel::NotPrivileged => RunasCommand::new(tool_path).status()?,
_ => StdCommand::new(tool_path)
.status()?,
_ => StdCommand::new(tool_path).status()?,
};
Ok(())

View File

@@ -4,9 +4,8 @@ mod merge;
mod script;
mod tun;
pub(self) use self::field::*;
use self::chain::*;
use self::field::*;
use self::merge::*;
use self::script::*;
use self::tun::*;

View File

@@ -34,6 +34,7 @@ fn main() -> std::io::Result<()> {
cmds::open_logs_dir,
cmds::open_web_url,
cmds::open_core_dir,
cmds::get_portable_flag,
// cmds::kill_sidecar,
cmds::restart_sidecar,
cmds::grant_permission,
@@ -131,6 +132,9 @@ fn main() -> std::io::Result<()> {
tauri::RunEvent::WindowEvent { label, event, .. } => {
if label == "main" {
match event {
tauri::WindowEvent::Destroyed => {
let _ = resolve::save_window_size_position(&app_handle, true);
}
tauri::WindowEvent::CloseRequested { .. } => {
let _ = resolve::save_window_size_position(&app_handle, true);
}

View File

@@ -1,86 +1,69 @@
use crate::core::handle;
use anyhow::Result;
use once_cell::sync::OnceCell;
use std::path::PathBuf;
use tauri::{
api::path::{data_dir, resource_dir},
Env, PackageInfo,
Env,
};
#[cfg(not(feature = "verge-dev"))]
static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev";
#[cfg(feature = "verge-dev")]
static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
pub static APP_ID: &str = "io.github.clash-verge-rev.clash-verge-rev.dev";
pub static PORTABLE_FLAG: OnceCell<bool> = OnceCell::new();
static CLASH_CONFIG: &str = "config.yaml";
static VERGE_CONFIG: &str = "verge.yaml";
static PROFILE_YAML: &str = "profiles.yaml";
static mut RESOURCE_DIR: Option<PathBuf> = None;
static mut APP_HOME_DIR: Option<PathBuf> = None;
/// portable flag
#[allow(unused)]
static mut PORTABLE_FLAG: bool = false;
pub static mut APP_VERSION: &str = "v1.2.0";
/// initialize portable flag
#[cfg(target_os = "windows")]
pub unsafe fn init_portable_flag() -> Result<()> {
/// init portable flag
pub fn init_portable_flag() -> Result<()> {
use tauri::utils::platform::current_exe;
let exe = current_exe()?;
if let Some(dir) = exe.parent() {
let app_exe = current_exe()?;
if let Some(dir) = app_exe.parent() {
let dir = PathBuf::from(dir).join(".config/PORTABLE");
if dir.exists() {
PORTABLE_FLAG = true;
PORTABLE_FLAG.get_or_init(|| true);
}
}
PORTABLE_FLAG.get_or_init(|| false);
Ok(())
}
/// get the verge app home dir
pub fn app_home_dir() -> Result<PathBuf> {
use tauri::utils::platform::current_exe;
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_dir = app_exe
.parent()
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
let portable_home_dir = PathBuf::from(app_dir).join(".config").join(APP_ID);
let home_dir = data_dir()
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
.join(APP_ID);
unsafe {
if PORTABLE_FLAG {
APP_HOME_DIR = Some(portable_home_dir.clone());
Ok(portable_home_dir)
} else {
APP_HOME_DIR = Some(home_dir.clone());
Ok(home_dir)
}
let flag = PORTABLE_FLAG.get().unwrap_or(&false);
if *flag {
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_dir = app_exe
.parent()
.ok_or(anyhow::anyhow!("failed to get the portable app dir"))?;
return Ok(PathBuf::from(app_dir).join(".config").join(APP_ID));
}
Ok(data_dir()
.ok_or(anyhow::anyhow!("failed to get app home dir"))?
.join(APP_ID))
}
/// get the resources dir
pub fn app_resources_dir(package_info: &PackageInfo) -> Result<PathBuf> {
let res_dir = resource_dir(package_info, &Env::default())
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
.join("resources");
unsafe {
RESOURCE_DIR = Some(res_dir.clone());
let ver = package_info.version.to_string();
let ver_str = format!("v{ver}");
APP_VERSION = Box::leak(Box::new(ver_str));
}
Ok(res_dir)
pub fn app_resources_dir() -> Result<PathBuf> {
let handle = handle::Handle::global();
let app_handle = handle.app_handle.lock();
if let Some(app_handle) = app_handle.as_ref() {
let res_dir = resource_dir(app_handle.package_info(), &Env::default())
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?
.join("resources");
return Ok(res_dir);
};
Err(anyhow::anyhow!("failed to get the resource dir"))
}
/// profiles dir
@@ -105,32 +88,18 @@ pub fn profiles_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join(PROFILE_YAML))
}
#[allow(unused)]
pub fn app_res_dir() -> Result<PathBuf> {
unsafe {
Ok(RESOURCE_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?)
}
pub fn clash_pid_path() -> Result<PathBuf> {
Ok(app_home_dir()?.join("clash.pid"))
}
pub fn clash_pid_path() -> Result<PathBuf> {
unsafe {
Ok(APP_HOME_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the app home dir"))?
.join("clash.pid"))
}
#[cfg(windows)]
pub fn service_dir() -> Result<PathBuf> {
Ok(app_home_dir()?.join("service"))
}
#[cfg(windows)]
pub fn service_path() -> Result<PathBuf> {
unsafe {
let res_dir = RESOURCE_DIR
.clone()
.ok_or(anyhow::anyhow!("failed to get the resource dir"))?;
Ok(res_dir.join("clash-verge-service.exe"))
}
Ok(service_dir()?.join("clash-verge-service.exe"))
}
#[cfg(windows)]

View File

@@ -71,15 +71,12 @@ pub fn get_uid(prefix: &str) -> String {
/// parse the string
/// xxx=123123; => 123123
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
target.find(key).and_then(|idx| {
let idx = idx + key.len();
let value = &target[idx..];
match value.split(';').nth(0) {
Some(value) => value.trim().parse(),
None => value.trim().parse(),
target.split(';').map(str::trim).find_map(|s| {
let mut parts = s.splitn(2, '=');
match (parts.next(), parts.next()) {
(Some(k), Some(v)) if k == key => v.parse::<T>().ok(),
_ => None,
}
.ok()
})
}
@@ -162,17 +159,17 @@ fn test_parse_value() {
let test_1 = "upload=111; download=2222; total=3333; expire=444";
let test_2 = "attachment; filename=Clash.yaml";
assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
assert_eq!(parse_str::<usize>(test_1, "upload").unwrap(), 111);
assert_eq!(parse_str::<usize>(test_1, "download").unwrap(), 2222);
assert_eq!(parse_str::<usize>(test_1, "total").unwrap(), 3333);
assert_eq!(parse_str::<usize>(test_1, "expire").unwrap(), 444);
assert_eq!(
parse_str::<String>(test_2, "filename=").unwrap(),
parse_str::<String>(test_2, "filename").unwrap(),
format!("Clash.yaml")
);
assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
assert_eq!(parse_str::<usize>(test_1, "aaa"), None);
assert_eq!(parse_str::<usize>(test_1, "upload1"), None);
assert_eq!(parse_str::<usize>(test_1, "expire1"), None);
assert_eq!(parse_str::<usize>(test_2, "attachment"), None);
}

View File

@@ -9,7 +9,6 @@ use log4rs::config::{Appender, Logger, Root};
use log4rs::encode::pattern::PatternEncoder;
use std::fs::{self, DirEntry};
use std::str::FromStr;
use tauri::PackageInfo;
/// initialize this instance's log file
fn init_log() -> Result<()> {
@@ -116,7 +115,10 @@ pub fn delete_log() -> Result<()> {
if file_name.ends_with(".log") {
let now = Local::now();
let created_time = parse_time_str(&file_name[0..file_name.len() - 4])?;
let file_time = Local.from_local_datetime(&created_time).single().ok_or(anyhow::anyhow!("invalid local datetime"))?;
let file_time = Local
.from_local_datetime(&created_time)
.single()
.ok_or(anyhow::anyhow!("invalid local datetime"))?;
let duration = now.signed_duration_since(file_time);
if duration.num_days() > day {
@@ -139,11 +141,7 @@ pub fn delete_log() -> Result<()> {
/// Initialize all the config files
/// before tauri setup
pub fn init_config() -> Result<()> {
#[cfg(target_os = "windows")]
unsafe {
let _ = dirs::init_portable_flag();
}
let _ = dirs::init_portable_flag();
let _ = init_log();
let _ = delete_log();
@@ -185,9 +183,9 @@ pub fn init_config() -> Result<()> {
/// initialize app resources
/// after tauri setup
pub fn init_resources(package_info: &PackageInfo) -> Result<()> {
pub fn init_resources() -> Result<()> {
let app_dir = dirs::app_home_dir()?;
let res_dir = dirs::app_resources_dir(package_info)?;
let res_dir = dirs::app_resources_dir()?;
if !app_dir.exists() {
let _ = fs::create_dir_all(&app_dir);
@@ -241,3 +239,95 @@ pub fn init_resources(package_info: &PackageInfo) -> Result<()> {
Ok(())
}
/// initialize service resources
/// after tauri setup
#[cfg(target_os = "windows")]
pub fn init_service() -> Result<()> {
let service_dir = dirs::service_dir()?;
let res_dir = dirs::app_resources_dir()?;
if !service_dir.exists() {
let _ = fs::create_dir_all(&service_dir);
}
if !res_dir.exists() {
let _ = fs::create_dir_all(&res_dir);
}
let file_list = [
"clash-verge-service.exe",
"install-service.exe",
"uninstall-service.exe",
];
// copy the resource file
// if the source file is newer than the destination file, copy it over
for file in file_list.iter() {
let src_path = res_dir.join(file);
let dest_path = service_dir.join(file);
let handle_copy = || {
match fs::copy(&src_path, &dest_path) {
Ok(_) => log::debug!(target: "app", "resources copied '{file}'"),
Err(err) => {
log::error!(target: "app", "failed to copy resources '{file}', {err}")
}
};
};
if src_path.exists() && !dest_path.exists() {
handle_copy();
continue;
}
let src_modified = fs::metadata(&src_path).and_then(|m| m.modified());
let dest_modified = fs::metadata(&dest_path).and_then(|m| m.modified());
match (src_modified, dest_modified) {
(Ok(src_modified), Ok(dest_modified)) => {
if src_modified > dest_modified {
handle_copy();
} else {
log::debug!(target: "app", "skipping resource copy '{file}'");
}
}
_ => {
log::debug!(target: "app", "failed to get modified '{file}'");
handle_copy();
}
};
}
Ok(())
}
/// initialize url scheme
#[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe;
use winreg::enums::*;
use winreg::RegKey;
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_exe = app_exe.to_string_lossy().to_owned();
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
let (clash, _) = hkcu.create_subkey("Software\\Classes\\Clash")?;
clash.set_value("", &"Clash Verge")?;
clash.set_value("URL Protocol", &"Clash Verge URL Scheme Protocol")?;
let (default_icon, _) = hkcu.create_subkey("Software\\Classes\\Clash\\DefaultIcon")?;
default_icon.set_value("", &format!("{app_exe}"))?;
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
command.set_value("", &format!("{app_exe} \"%1\""))?;
Ok(())
}
#[cfg(target_os = "linux")]
pub fn init_scheme() -> Result<()> {
Ok(())
}
#[cfg(target_os = "macos")]
pub fn init_scheme() -> Result<()> {
Ok(())
}

View File

@@ -4,4 +4,3 @@ pub mod init;
pub mod resolve;
pub mod server;
pub mod tmpl;
// mod winhelp;

View File

@@ -1,11 +1,20 @@
use crate::config::IVerge;
use crate::{config::Config, core::*, utils::init, utils::server};
use crate::config::{IVerge, PrfOption};
use crate::{
config::{Config, PrfItem},
core::*,
utils::init,
utils::server,
};
use crate::{log_err, trace_err};
use anyhow::Result;
use once_cell::sync::OnceCell;
use serde_yaml::Mapping;
use std::net::TcpListener;
use tauri::api::notification;
use tauri::{App, AppHandle, Manager};
pub static VERSION: OnceCell<String> = OnceCell::new();
pub fn find_unused_port() -> Result<u16> {
match TcpListener::bind("127.0.0.1:0") {
Ok(listener) => {
@@ -27,10 +36,14 @@ pub fn find_unused_port() -> Result<u16> {
pub fn resolve_setup(app: &mut App) {
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Accessory);
let version = app.package_info().version.to_string();
handle::Handle::global().init(app.app_handle());
VERSION.get_or_init(|| version.clone());
log_err!(init::init_resources(app.package_info()));
log_err!(init::init_resources());
#[cfg(target_os = "windows")]
log_err!(init::init_service());
log_err!(init::init_scheme());
// 处理随机端口
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
@@ -84,6 +97,13 @@ pub fn resolve_setup(app: &mut App) {
log_err!(handle::Handle::update_systray_part());
log_err!(hotkey::Hotkey::global().init(app.app_handle()));
log_err!(timer::Timer::global().init());
let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 {
tauri::async_runtime::block_on(async {
resolve_scheme(argvs[1].to_owned()).await;
});
}
}
/// reset system proxy
@@ -195,6 +215,13 @@ pub fn create_window(app_handle: &AppHandle) {
/// save window size and position
pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) -> Result<()> {
let verge = Config::verge();
let mut verge = verge.latest();
if save_to_file {
verge.save_file()?;
}
let win = app_handle
.get_window("main")
.ok_or(anyhow::anyhow!("failed to get window"))?;
@@ -205,13 +232,35 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
let pos = win.outer_position()?;
let pos = pos.to_logical::<f64>(scale);
let verge = Config::verge();
let mut verge = verge.latest();
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
if save_to_file {
verge.save_file()?;
if size.width >= 600.0 && size.height >= 520.0 {
verge.window_size_position = Some(vec![size.width, size.height, pos.x, pos.y]);
}
Ok(())
}
pub async fn resolve_scheme(param: String) {
let url = param.trim_start_matches("clash://install-config/?url=");
let option = PrfOption {
user_agent: None,
with_proxy: Some(true),
self_proxy: None,
update_interval: None,
};
if let Ok(item) = PrfItem::from_url(&url, None, None, Some(option)).await {
if let Ok(_) = Config::profiles().data().append_item(item) {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile success")
.show()
.unwrap();
};
} else {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile failed")
.show()
.unwrap();
log::error!("failed to parse url: {}", url);
}
}

View File

@@ -4,19 +4,42 @@ use super::resolve;
use crate::config::IVerge;
use anyhow::{bail, Result};
use port_scanner::local_port_available;
use std::convert::Infallible;
use tauri::AppHandle;
use warp::Filter;
#[derive(serde::Deserialize, Debug)]
struct QueryParam {
param: String,
}
/// check whether there is already exists
pub fn check_singleton() -> Result<()> {
let port = IVerge::get_singleton_port();
if !local_port_available(port) {
tauri::async_runtime::block_on(async {
let url = format!("http://127.0.0.1:{port}/commands/visible");
let resp = reqwest::get(url).await?.text().await?;
let resp = reqwest::get(format!("http://127.0.0.1:{port}/commands/ping"))
.await?
.text()
.await?;
if &resp == "ok" {
let argvs: Vec<String> = std::env::args().collect();
if argvs.len() > 1 {
let param = argvs[1].as_str();
reqwest::get(format!(
"http://127.0.0.1:{port}/commands/scheme?param={param}"
))
.await?
.text()
.await?;
} else {
reqwest::get(format!("http://127.0.0.1:{port}/commands/visible"))
.await?
.text()
.await?;
}
bail!("app exists");
}
@@ -34,11 +57,22 @@ pub fn embed_server(app_handle: AppHandle) {
let port = IVerge::get_singleton_port();
tauri::async_runtime::spawn(async move {
let commands = warp::path!("commands" / "visible").map(move || {
let ping = warp::path!("commands" / "ping").map(move || "ok");
let visible = warp::path!("commands" / "visible").map(move || {
resolve::create_window(&app_handle);
format!("ok")
"ok"
});
warp::serve(commands).bind(([127, 0, 0, 1], port)).await;
let scheme = warp::path!("commands" / "scheme")
.and(warp::query::<QueryParam>())
.and_then(scheme_handler);
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
resolve::resolve_scheme(query.param).await;
Ok("ok")
}
let commands = ping.or(visible).or(scheme);
warp::serve(commands).run(([127, 0, 0, 1], port)).await;
});
}

View File

@@ -1,4 +1,4 @@
///! Some config file template
//! Some config file template
/// template for new a profile item
pub const ITEM_LOCAL: &str = "# Profile Template for clash verge

View File

@@ -1,69 +0,0 @@
#![cfg(target_os = "windows")]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
//!
//! From https://github.com/tauri-apps/window-vibrancy/blob/dev/src/windows.rs
//!
use windows_sys::Win32::{
Foundation::*,
System::{LibraryLoader::*, SystemInformation::*},
};
fn get_function_impl(library: &str, function: &str) -> Option<FARPROC> {
assert_eq!(library.chars().last(), Some('\0'));
assert_eq!(function.chars().last(), Some('\0'));
let module = unsafe { LoadLibraryA(library.as_ptr()) };
if module == 0 {
return None;
}
Some(unsafe { GetProcAddress(module, function.as_ptr()) })
}
macro_rules! get_function {
($lib:expr, $func:ident) => {
get_function_impl(concat!($lib, '\0'), concat!(stringify!($func), '\0')).map(|f| unsafe {
std::mem::transmute::<::windows_sys::Win32::Foundation::FARPROC, $func>(f)
})
};
}
/// Returns a tuple of (major, minor, buildnumber)
fn get_windows_ver() -> Option<(u32, u32, u32)> {
type RtlGetVersion = unsafe extern "system" fn(*mut OSVERSIONINFOW) -> i32;
let handle = get_function!("ntdll.dll", RtlGetVersion);
if let Some(rtl_get_version) = handle {
unsafe {
let mut vi = OSVERSIONINFOW {
dwOSVersionInfoSize: 0,
dwMajorVersion: 0,
dwMinorVersion: 0,
dwBuildNumber: 0,
dwPlatformId: 0,
szCSDVersion: [0; 128],
};
let status = (rtl_get_version)(&mut vi as _);
if status >= 0 {
Some((vi.dwMajorVersion, vi.dwMinorVersion, vi.dwBuildNumber))
} else {
None
}
}
} else {
None
}
}
pub fn is_win11() -> bool {
let v = get_windows_ver().unwrap_or_default();
v.2 >= 22000
}
#[test]
fn test_version() {
dbg!(get_windows_ver().unwrap_or_default());
}

View File

@@ -1,7 +1,7 @@
{
"package": {
"productName": "Clash Verge",
"version": "1.4.4"
"version": "1.4.6"
},
"build": {
"distDir": "../dist",
@@ -51,6 +51,9 @@
},
"clipboard": {
"all": true
},
"notification": {
"all": true
}
},
"windows": [],

View File

@@ -1,7 +1,7 @@
{
"tauri": {
"systemTray": {
"iconPath": "icons/icon.png",
"iconPath": "icons/mac-tray-icon.png",
"iconAsTemplate": true
},
"bundle": {

View File

@@ -77,8 +77,8 @@
.the-bar {
position: absolute;
top: 2px;
right: 8px;
top: 0px;
right: 0px;
height: 36px;
display: flex;
align-items: center;

View File

@@ -27,15 +27,25 @@
width: 100%;
height: 100%;
overflow: auto;
padding: 5px 5px;
padding: 10px 0;
box-sizing: border-box;
scrollbar-gutter: stable;
.base-content {
width: 100%;
// max-width: 850px;
width: calc(100% - 10px * 2);
margin: 0 auto;
}
}
&.no-padding {
> section {
padding: 0;
overflow: visible;
.base-content {
width: 100%;
}
}
}
}
}

View File

@@ -8,17 +8,18 @@ interface Props {
header?: React.ReactNode; // something behind title
contentStyle?: React.CSSProperties;
children?: ReactNode;
full?: boolean;
}
export const BasePage: React.FC<Props> = (props) => {
const { title, header, contentStyle, children } = props;
const { title, header, contentStyle, full, children } = props;
const { theme } = useCustomTheme();
const isDark = theme.palette.mode === "dark";
return (
<BaseErrorBoundary>
<div className="base-page" data-windrag>
<div className="base-page">
<header data-windrag style={{ userSelect: "none" }}>
<Typography variant="h4" component="h1" data-windrag>
{title}
@@ -28,7 +29,7 @@ export const BasePage: React.FC<Props> = (props) => {
</header>
<div
className="base-container"
className={full ? "base-container no-padding" : "base-container"}
style={{ backgroundColor: isDark ? "#090909" : "#ffffff" }}
>
<section
@@ -38,7 +39,7 @@ export const BasePage: React.FC<Props> = (props) => {
: "",
}}
>
<div className="base-content" style={contentStyle} data-windrag>
<div className="base-content" style={contentStyle}>
{children}
</div>
</section>

View File

@@ -1,16 +1,50 @@
import { Button } from "@mui/material";
import { Button, ButtonGroup } from "@mui/material";
import { appWindow } from "@tauri-apps/api/window";
import {
CloseRounded,
CropSquareRounded,
FilterNoneRounded,
HorizontalRuleRounded,
PushPinOutlined,
PushPinRounded,
} from "@mui/icons-material";
import { useState } from "react";
export const LayoutControl = () => {
const minWidth = 40;
const [isMaximized, setIsMaximized] = useState(false);
const [isPined, setIsPined] = useState(false);
appWindow.isMaximized().then((isMaximized) => {
setIsMaximized(() => isMaximized);
});
return (
<>
<ButtonGroup
variant="text"
sx={{
height: "100%",
".MuiButtonGroup-grouped": {
borderRadius: "0px",
borderRight: "0px",
},
}}
>
<Button
size="small"
sx={{ minWidth, svg: { transform: "scale(0.9)" } }}
onClick={() => {
appWindow.setAlwaysOnTop(!isPined);
setIsPined((isPined) => !isPined);
}}
>
{isPined ? (
<PushPinRounded fontSize="small" />
) : (
<PushPinOutlined fontSize="small" />
)}
</Button>
<Button
size="small"
sx={{ minWidth, svg: { transform: "scale(0.9)" } }}
@@ -22,18 +56,34 @@ export const LayoutControl = () => {
<Button
size="small"
sx={{ minWidth, svg: { transform: "scale(0.9)" } }}
onClick={() => appWindow.toggleMaximize()}
onClick={() => {
setIsMaximized((isMaximized) => !isMaximized);
appWindow.toggleMaximize();
}}
>
<CropSquareRounded fontSize="small" />
{isMaximized ? (
<FilterNoneRounded
fontSize="small"
style={{
transform: "rotate(180deg) scale(0.7)",
}}
/>
) : (
<CropSquareRounded fontSize="small" />
)}
</Button>
<Button
size="small"
sx={{ minWidth, svg: { transform: "scale(1.05)" } }}
sx={{
minWidth,
svg: { transform: "scale(1.05)" },
":hover": { bgcolor: "#ff000090" },
}}
onClick={() => appWindow.close()}
>
<CloseRounded fontSize="small" />
</Button>
</>
</ButtonGroup>
);
};

View File

@@ -4,7 +4,8 @@ import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { useTranslation } from "react-i18next";
import { useVerge } from "@/hooks/use-verge";
import { useLockFn } from "ahooks";
import { Lock } from "@mui/icons-material";
import { LoadingButton } from "@mui/lab";
import { SwitchAccessShortcut, RestartAlt } from "@mui/icons-material";
import {
Box,
Button,
@@ -31,6 +32,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
const { verge, mutateVerge } = useVerge();
const [open, setOpen] = useState(false);
const [upgrading, setUpgrading] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setOpen(true),
@@ -78,9 +80,12 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
const onUpgrade = useLockFn(async () => {
try {
setUpgrading(true);
await upgradeCore();
setUpgrading(false);
Notice.success(`Successfully upgrade core`, 1000);
} catch (err: any) {
setUpgrading(false);
Notice.error(err?.response.data.message || err.toString());
}
});
@@ -93,16 +98,24 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
{t("Clash Core")}
<Box>
{clash_core !== "clash-meta" && (
<Button
<LoadingButton
variant="contained"
size="small"
startIcon={<SwitchAccessShortcut />}
loadingPosition="start"
loading={upgrading}
sx={{ marginRight: "8px" }}
onClick={onUpgrade}
>
{t("Upgrade")}
</Button>
</LoadingButton>
)}
<Button variant="contained" size="small" onClick={onRestart}>
<Button
variant="contained"
size="small"
onClick={onRestart}
startIcon={<RestartAlt />}
>
{t("Restart")}
</Button>
</Box>
@@ -110,7 +123,7 @@ export const ClashCoreViewer = forwardRef<DialogRef>((props, ref) => {
}
contentSx={{
pb: 0,
width: 320,
width: 400,
height: 180,
overflowY: "auto",
userSelect: "text",

View File

@@ -20,8 +20,8 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
const [open, setOpen] = useState(false);
const [values, setValues] = useState({
appLogLevel: "info",
autoCloseConnection: false,
enableClashFields: false,
autoCloseConnection: true,
enableClashFields: true,
enableBuiltinEnhanced: true,
proxyLayoutColumn: 6,
defaultLatencyTest: "",
@@ -34,7 +34,7 @@ export const MiscViewer = forwardRef<DialogRef>((props, ref) => {
setValues({
appLogLevel: verge?.app_log_level ?? "info",
autoCloseConnection: verge?.auto_close_connection ?? true,
enableClashFields: verge?.enable_clash_fields ?? false,
enableClashFields: verge?.enable_clash_fields ?? true,
enableBuiltinEnhanced: verge?.enable_builtin_enhanced ?? true,
proxyLayoutColumn: verge?.proxy_layout_column || 6,
defaultLatencyTest: verge?.default_latency_test || "",

View File

@@ -168,6 +168,6 @@ const FlexBox = styled("div")`
.label {
flex: none;
width: 80px;
width: 85px;
}
`;

View File

@@ -42,7 +42,7 @@ const SettingClash = ({ onError }: Props) => {
const {
enable_random_port = false,
verge_mixed_port,
enable_clash_fields = false,
enable_clash_fields = true,
} = verge ?? {};
const webRef = useRef<DialogRef>(null);

View File

@@ -18,6 +18,7 @@ import { GuardState } from "./mods/guard-state";
import { LayoutViewer } from "./mods/layout-viewer";
import { UpdateViewer } from "./mods/update-viewer";
import getSystem from "@/utils/get-system";
import { portableFlag } from "@/pages/_layout";
interface Props {
onError?: (err: Error) => void;
@@ -111,7 +112,7 @@ const SettingVerge = ({ onError }: Props) => {
<SettingItem label={t("Copy Env Type")}>
<GuardState
value={env_type ?? OS === "windows" ? "powershell" : "bash"}
value={env_type ?? (OS === "windows" ? "powershell" : "bash")}
onCatch={onError}
onFormat={(e: any) => e.target.value}
onChange={(e) => onChangeData({ env_type: e })}
@@ -213,7 +214,7 @@ const SettingVerge = ({ onError }: Props) => {
</IconButton>
</SettingItem>
{!(OS === "windows" && WIN_PORTABLE) && (
{!portableFlag && (
<SettingItem label={t("Check for Updates")}>
<IconButton
color="inherit"

View File

@@ -22,6 +22,9 @@ import { useCustomTheme } from "@/components/layout/use-custom-theme";
import getSystem from "@/utils/get-system";
import "dayjs/locale/ru";
import "dayjs/locale/zh-cn";
import { getPortableFlag } from "@/services/cmds";
export let portableFlag = false;
dayjs.extend(relativeTime);
@@ -71,10 +74,12 @@ const Layout = () => {
break;
}
});
setTimeout(() => {
void appWindow.unminimize();
void appWindow.show();
void appWindow.setFocus();
setTimeout(async () => {
portableFlag = await getPortableFlag();
await appWindow.unminimize();
await appWindow.show();
await appWindow.setFocus();
}, 50);
}, []);
@@ -119,9 +124,7 @@ const Layout = () => {
<div className="the-logo" data-windrag>
<LogoSvg />
{!(OS === "windows" && WIN_PORTABLE) && (
<UpdateButton className="the-newbtn" />
)}
{!portableFlag && <UpdateButton className="the-newbtn" />}
</div>
<List className="the-menu">

View File

@@ -114,6 +114,7 @@ const ConnectionsPage = () => {
return (
<BasePage
full
title={t("Connections")}
contentStyle={{ height: "100%" }}
header={
@@ -142,75 +143,72 @@ const ConnectionsPage = () => {
</Box>
}
>
<Box sx={{ boxShadow: 0, height: "100%" }}>
<Box
sx={{
pt: 1,
mb: 0.5,
mx: "12px",
height: "36px",
display: "flex",
alignItems: "center",
userSelect: "text",
}}
>
{!isTableLayout && (
<Select
size="small"
autoComplete="off"
value={curOrderOpt}
onChange={(e) => setOrderOpt(e.target.value)}
sx={{
mr: 1,
width: i18n.language === "en" ? 190 : 120,
'[role="button"]': { py: 0.65 },
}}
>
{Object.keys(orderOpts).map((opt) => (
<MenuItem key={opt} value={opt}>
<span style={{ fontSize: 14 }}>{t(opt)}</span>
</MenuItem>
))}
</Select>
)}
<TextField
hiddenLabel
fullWidth
<Box
sx={{
pt: 1,
mb: 0.5,
mx: "10px",
height: "36px",
display: "flex",
alignItems: "center",
userSelect: "text",
}}
>
{!isTableLayout && (
<Select
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>
value={curOrderOpt}
onChange={(e) => setOrderOpt(e.target.value)}
sx={{
mr: 1,
width: i18n.language === "en" ? 190 : 120,
'[role="button"]': { py: 0.65 },
}}
>
{Object.keys(orderOpts).map((opt) => (
<MenuItem key={opt} value={opt}>
<span style={{ fontSize: 14 }}>{t(opt)}</span>
</MenuItem>
))}
</Select>
)}
<Box height="calc(100% - 50px)" sx={{ userSelect: "text" }}>
{filterConn.length === 0 ? (
<BaseEmpty text="No Connections" />
) : isTableLayout ? (
<ConnectionTable
connections={filterConn}
onShowDetail={(detail) => detailRef.current?.open(detail)}
/>
) : (
<Virtuoso
data={filterConn}
itemContent={(index, item) => (
<ConnectionItem
value={item}
onShowDetail={() => detailRef.current?.open(item)}
/>
)}
/>
)}
</Box>
<ConnectionDetail ref={detailRef} />
<TextField
hiddenLabel
fullWidth
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>
<Box height="calc(100% - 50px)" sx={{ userSelect: "text" }}>
{filterConn.length === 0 ? (
<BaseEmpty text="No Connections" />
) : isTableLayout ? (
<ConnectionTable
connections={filterConn}
onShowDetail={(detail) => detailRef.current?.open(detail)}
/>
) : (
<Virtuoso
data={filterConn}
itemContent={(index, item) => (
<ConnectionItem
value={item}
onShowDetail={() => detailRef.current?.open(item)}
/>
)}
/>
)}
</Box>
<ConnectionDetail ref={detailRef} />
</BasePage>
);
};

View File

@@ -38,6 +38,7 @@ const LogPage = () => {
return (
<BasePage
full
title={t("Logs")}
contentStyle={{ height: "100%" }}
header={
@@ -66,61 +67,52 @@ const LogPage = () => {
>
<Box
sx={{
boxSizing: "border-box",
boxShadow: 0,
height: "100%",
userSelect: "text",
pt: 1,
mb: 0.5,
mx: "10px",
height: "36px",
display: "flex",
alignItems: "center",
}}
>
<Box
sx={{
pt: 1,
mb: 0.5,
mx: "12px",
height: "36px",
display: "flex",
alignItems: "center",
}}
<Select
size="small"
autoComplete="off"
value={logState}
onChange={(e) => setLogState(e.target.value)}
sx={{ width: 120, mr: 1, '[role="button"]': { py: 0.65 } }}
>
<Select
size="small"
autoComplete="off"
value={logState}
onChange={(e) => setLogState(e.target.value)}
sx={{ width: 120, mr: 1, '[role="button"]': { py: 0.65 } }}
>
<MenuItem value="all">ALL</MenuItem>
<MenuItem value="inf">INFO</MenuItem>
<MenuItem value="warn">WARN</MenuItem>
<MenuItem value="err">ERROR</MenuItem>
</Select>
<MenuItem value="all">ALL</MenuItem>
<MenuItem value="inf">INFO</MenuItem>
<MenuItem value="warn">WARN</MenuItem>
<MenuItem value="err">ERROR</MenuItem>
</Select>
<TextField
hiddenLabel
fullWidth
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
<TextField
hiddenLabel
fullWidth
size="small"
autoComplete="off"
spellCheck="false"
variant="outlined"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>
<Box height="calc(100% - 50px)">
{filterLogs.length > 0 ? (
<Virtuoso
initialTopMostItemIndex={999}
data={filterLogs}
itemContent={(index, item) => <LogItem value={item} />}
followOutput={"smooth"}
/>
</Box>
<Box height="calc(100% - 50px)">
{filterLogs.length > 0 ? (
<Virtuoso
initialTopMostItemIndex={999}
data={filterLogs}
itemContent={(index, item) => <LogItem value={item} />}
followOutput={"smooth"}
/>
) : (
<BaseEmpty text="No Logs" />
)}
</Box>
) : (
<BaseEmpty text="No Logs" />
)}
</Box>
</BasePage>
);

View File

@@ -51,6 +51,7 @@ const ProxyPage = () => {
return (
<BasePage
full
contentStyle={{ height: "100%" }}
title={t("Proxy Groups")}
header={
@@ -72,16 +73,7 @@ const ProxyPage = () => {
</Box>
}
>
<Box
sx={{
borderRadius: 1,
boxShadow: 0,
height: "100%",
boxSizing: "border-box",
}}
>
<ProxyGroups mode={curMode!} />
</Box>
<ProxyGroups mode={curMode!} />
</BasePage>
);
};

View File

@@ -18,45 +18,43 @@ const RulesPage = () => {
}, [data, filterText]);
return (
<BasePage title={t("Rules")} contentStyle={{ height: "100%" }}>
<Box sx={{ boxSizing: "border-box", boxShadow: 0, height: "100%" }}>
<Box
sx={{
pt: 1,
mb: 0.5,
mx: "12px",
height: "36px",
display: "flex",
alignItems: "center",
}}
>
<TextField
hiddenLabel
fullWidth
size="small"
autoComplete="off"
variant="outlined"
spellCheck="false"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>
<BasePage full title={t("Rules")} contentStyle={{ height: "100%" }}>
<Box
sx={{
pt: 1,
mb: 0.5,
mx: "10px",
height: "36px",
display: "flex",
alignItems: "center",
}}
>
<TextField
hiddenLabel
fullWidth
size="small"
autoComplete="off"
variant="outlined"
spellCheck="false"
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>
<Box height="calc(100% - 50px)">
{rules.length > 0 ? (
<Virtuoso
data={rules}
itemContent={(index, item) => (
<RuleItem index={index + 1} value={item} />
)}
followOutput={"smooth"}
/>
) : (
<BaseEmpty text="No Rules" />
)}
</Box>
<Box height="calc(100% - 50px)">
{rules.length > 0 ? (
<Virtuoso
data={rules}
itemContent={(index, item) => (
<RuleItem index={index + 1} value={item} />
)}
followOutput={"smooth"}
/>
) : (
<BaseEmpty text="No Rules" />
)}
</Box>
</BasePage>
);

View File

@@ -191,3 +191,7 @@ export async function invoke_uwp_tool() {
Notice.error(err?.message || err.toString(), 1500)
);
}
export async function getPortableFlag() {
return invoke<boolean>("get_portable_flag");
}

View File

@@ -14,7 +14,6 @@ type Platform =
/**
* defines in `vite.config.ts`
*/
declare const WIN_PORTABLE: boolean;
declare const OS_PLATFORM: Platform;
/**

View File

@@ -25,6 +25,5 @@ export default defineConfig({
},
define: {
OS_PLATFORM: `"${process.platform}"`,
WIN_PORTABLE: !!process.env.VITE_WIN_PORTABLE,
},
});