Compare commits
28 Commits
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -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:
|
||||
|
||||
25
README.md
25
README.md
@@ -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!
|
||||
|
||||
|
||||
34
UPDATELOG.md
34
UPDATELOG.md
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clash-verge",
|
||||
"version": "1.4.4",
|
||||
"version": "1.4.6",
|
||||
"license": "GPL-3.0",
|
||||
"scripts": {
|
||||
"dev": "tauri dev",
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
362
src-tauri/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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 |
BIN
src-tauri/icons/mac-tray-icon.png
Normal file
BIN
src-tauri/icons/mac-tray-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
@@ -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::*;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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}\"")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -4,4 +4,3 @@ pub mod init;
|
||||
pub mod resolve;
|
||||
pub mod server;
|
||||
pub mod tmpl;
|
||||
// mod winhelp;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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": [],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"tauri": {
|
||||
"systemTray": {
|
||||
"iconPath": "icons/icon.png",
|
||||
"iconPath": "icons/mac-tray-icon.png",
|
||||
"iconAsTemplate": true
|
||||
},
|
||||
"bundle": {
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
|
||||
.the-bar {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 8px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 || "",
|
||||
|
||||
@@ -168,6 +168,6 @@ const FlexBox = styled("div")`
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
width: 80px;
|
||||
width: 85px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
1
src/services/types.d.ts
vendored
1
src/services/types.d.ts
vendored
@@ -14,7 +14,6 @@ type Platform =
|
||||
/**
|
||||
* defines in `vite.config.ts`
|
||||
*/
|
||||
declare const WIN_PORTABLE: boolean;
|
||||
declare const OS_PLATFORM: Platform;
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,5 @@ export default defineConfig({
|
||||
},
|
||||
define: {
|
||||
OS_PLATFORM: `"${process.platform}"`,
|
||||
WIN_PORTABLE: !!process.env.VITE_WIN_PORTABLE,
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user