Compare commits

..

22 Commits

45 changed files with 413 additions and 203 deletions

View File

@@ -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.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)
- [Windows x64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/Clash.Verge_1.4.8_x64-setup.exe)
- [Windows x86](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/Clash.Verge_1.4.8_x86-setup.exe)
- [Windows arm64](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/Clash.Verge_1.4.8_arm64-setup.exe)
- [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)
- [macOS intel](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/Clash.Verge_1.4.8_x64.dmg)
- [macOS apple](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/Clash.Verge_1.4.8_aarch64.dmg)
- [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)
- [Linux x64 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/clash-verge_1.4.8_amd64.AppImage)
- [Linux x64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/clash-verge_1.4.8_amd64.deb)
- [Linux x86 AppImage](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/clash-verge_1.4.8_i386.AppImage)
- [Linux x86 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/clash-verge_1.4.8_i386.deb)
- [Linux arm64 deb](https://github.com/clash-verge-rev/clash-verge-rev/releases/download/v1.4.8/clash-verge_1.4.8_arm64.deb)
Or you can build it yourself. Supports Windows, Linux and macOS 10.15+
@@ -72,7 +72,7 @@ You should install Rust and Nodejs, see [here](https://tauri.app/v1/guides/getti
pnpm i
```
Then download the clash binary... Or you can download it from [clash meta release](https://github.com/MetaCubeX/Clash.Meta/releases) and rename it according to [tauri config](https://tauri.studio/docs/api/config/#tauri.bundle.externalBin).
Then download the clash binary... Or you can download it from [clash meta release](https://github.com/MetaCubeX/Clash.Meta/releases) and rename it according to [tauri config](https://tauri.app/v1/api/config#bundleconfig.externalbin).
```shell
# force update to latest version

View File

@@ -1,3 +1,29 @@
## v1.4.8
### Features
- 连接页面总流量显示
### Bugs Fixes
- 连接页面数据排序错误
- 新建订阅时设置更新间隔无效
- Windows 拨号网络无法设置系统代理
- Windows 开启/关闭系统代理延迟(使用注册表即可)
- 删除无效的背景模糊选项
---
## v1.4.7
### Features
- Windows 便携版禁用应用内更新
- 支持代理组 Hidden 选项
- 支持 URL Scheme(MacOS & Linux)
---
## v1.4.6
### Features

View File

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

View File

@@ -123,7 +123,7 @@ index 6c207d9..d47dc33 100644
- if result == "\"\"" {
- anyhow::bail!("main function should return object");
- }
- return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
- Ok(serde_json::from_str::<Mapping>(result.as_str())?)
- });
-
- let mut out = outputs.lock().unwrap();

View File

@@ -57,7 +57,7 @@ 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-cgo",
"darwin-x64": "mihomo-darwin-amd64",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",
@@ -102,7 +102,7 @@ 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-x64": "mihomo-darwin-amd64",
"darwin-arm64": "mihomo-darwin-arm64",
"linux-x64": "mihomo-linux-amd64-compatible",
"linux-ia32": "mihomo-linux-386",

20
src-tauri/Cargo.lock generated
View File

@@ -30,9 +30,9 @@ dependencies = [
[[package]]
name = "ahash"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
dependencies = [
"cfg-if",
"getrandom 0.2.11",
@@ -580,7 +580,7 @@ dependencies = [
[[package]]
name = "clash-verge"
version = "1.4.6"
version = "1.4.8"
dependencies = [
"anyhow",
"auto-launch",
@@ -2188,7 +2188,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03bfb870879ce6a141b644653d63b203d290ec5f3b6919cf7b30cba06a164a5"
dependencies = [
"ahash 0.8.6",
"ahash 0.8.7",
"once_cell",
"regex 1.10.2",
]
@@ -4492,13 +4492,13 @@ dependencies = [
[[package]]
name = "sysproxy"
version = "0.3.0"
source = "git+https://github.com/zzzgydi/sysproxy-rs?branch=main#f8fab6a542c41cf3b973f4fe77d399756e3efff1"
source = "git+https://github.com/clash-verge-rev/sysproxy-rs?branch=main#79390614ede8252158bf775ffaabbec04d8a4359"
dependencies = [
"interfaces",
"iptools",
"thiserror",
"windows 0.52.0",
"winreg 0.10.1",
"winreg 0.52.0",
]
[[package]]
@@ -6344,18 +6344,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.28"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.28"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,6 +1,6 @@
[package]
name = "clash-verge"
version = "1.4.6"
version = "1.4.8"
description = "clash verge"
authors = ["zzzgydi", "wonfen", "MystiPanda"]
license = "GPL-3.0"
@@ -38,7 +38,7 @@ window-shadows = { version = "0.2" }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
reqwest = { version = "0.11", features = ["json", "rustls-tls"] }
sysproxy = { git="https://github.com/zzzgydi/sysproxy-rs", branch = "main" }
sysproxy = { git="https://github.com/clash-verge-rev/sysproxy-rs", branch = "main" }
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]

17
src-tauri/Info.plist Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Clash Verge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>clash</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Categories={{{categories}}}
Comment={{{comment}}}
Exec={{{exec}}} %u
Icon={{{icon}}}
Name={{{name}}}
Terminal=false
Type=Application
MimeType=x-scheme-handler/clash;

View File

@@ -219,7 +219,7 @@ pub fn open_app_dir() -> CmdResult<()> {
#[tauri::command]
pub fn open_core_dir() -> CmdResult<()> {
let core_dir = wrap_err!(tauri::utils::platform::current_exe())?;
let core_dir = core_dir.parent().ok_or(format!("failed to get core dir"))?;
let core_dir = core_dir.parent().ok_or("failed to get core dir")?;
wrap_err!(open::that(core_dir))
}
@@ -252,7 +252,7 @@ pub async fn clash_api_get_proxy_delay(
) -> CmdResult<clash_api::DelayRes> {
match clash_api::get_proxy_delay(name, url).await {
Ok(res) => Ok(res),
Err(err) => Err(format!("{}", err.to_string())),
Err(err) => Err(err.to_string()),
}
}

View File

@@ -65,8 +65,8 @@ impl IClashTemp {
let config = &self.0;
ClashInfo {
port: Self::guard_mixed_port(&config),
server: Self::guard_client_ctrl(&config),
port: Self::guard_mixed_port(config),
server: Self::guard_client_ctrl(config),
secret: config.get("secret").and_then(|value| match value {
Value::String(val_str) => Some(val_str.clone()),
Value::Bool(val_bool) => Some(val_bool.to_string()),
@@ -98,7 +98,7 @@ impl IClashTemp {
Some(val_str) => {
let val_str = val_str.trim();
let val = match val_str.starts_with(":") {
let val = match val_str.starts_with(':') {
true => format!("127.0.0.1{val_str}"),
false => val_str.to_owned(),
};

View File

@@ -8,7 +8,7 @@ use sysproxy::Sysproxy;
use super::Config;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct PrfItem {
pub uid: Option<String>,
@@ -101,24 +101,6 @@ impl PrfOption {
}
}
impl Default for PrfItem {
fn default() -> Self {
PrfItem {
uid: None,
itype: None,
name: None,
desc: None,
file: None,
url: None,
selected: None,
extra: None,
updated: None,
option: None,
file_data: None,
}
}
}
impl PrfItem {
/// From partial item
/// must contain `itype`
@@ -188,7 +170,8 @@ impl PrfItem {
let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false));
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 user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
@@ -213,27 +196,24 @@ impl PrfItem {
}
// 使用系统代理
else if with_proxy {
match Sysproxy::get_system_proxy() {
Ok(p @ Sysproxy { enable: true, .. }) => {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(p @ Sysproxy { enable: true, .. }) = Sysproxy::get_system_proxy() {
let proxy_scheme = format!("http://{}:{}", p.host, p.port);
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::http(&proxy_scheme) {
builder = builder.proxy(proxy);
}
_ => {}
};
if let Ok(proxy) = reqwest::Proxy::https(&proxy_scheme) {
builder = builder.proxy(proxy);
}
if let Ok(proxy) = reqwest::Proxy::all(&proxy_scheme) {
builder = builder.proxy(proxy);
}
}
}
let version = match VERSION.get() {
Some(v) => format!("clash-verge/v{}", v),
None => format!("clash-verge/unknown"),
None => "clash-verge/unknown".to_string(),
};
builder = builder.user_agent(user_agent.unwrap_or(version));
@@ -283,17 +263,21 @@ impl PrfItem {
}
None => None,
};
// parse the profile-update-interval
let option = match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None,
let option = match update_interval {
Some(val) => Some(PrfOption {
update_interval: Some(val),
..PrfOption::default()
}),
None => match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Err(_) => None,
},
None => None,
},
None => None,
};
let uid = help::get_uid("r");

View File

@@ -37,13 +37,13 @@ impl IProfiles {
profiles.items = Some(vec![]);
}
// compatible with the old old old version
profiles.items.as_mut().map(|items| {
if let Some(items) = profiles.items.as_mut() {
for item in items.iter_mut() {
if item.uid.is_none() {
item.uid = Some(help::get_uid("d"));
}
}
});
}
profiles
}
Err(err) => {
@@ -152,17 +152,19 @@ impl IProfiles {
self.items = Some(vec![]);
}
self.items.as_mut().map(|items| items.push(item));
if let Some(items) = self.items.as_mut() {
items.push(item)
}
self.save_file()
}
/// reorder items
pub fn reorder(&mut self, active_id: String, over_id: String) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
let mut items = self.items.take().unwrap_or_default();
let mut old_index = None;
let mut new_index = None;
for i in 0..items.len() {
for (i, _) in items.iter().enumerate() {
if items[i].uid == Some(active_id.clone()) {
old_index = Some(i);
}
@@ -182,7 +184,7 @@ impl IProfiles {
/// update the item value
pub fn patch_item(&mut self, uid: String, item: PrfItem) -> Result<()> {
let mut items = self.items.take().unwrap_or(vec![]);
let mut items = self.items.take().unwrap_or_default();
for each in items.iter_mut() {
if each.uid == Some(uid.clone()) {
@@ -255,11 +257,11 @@ impl IProfiles {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let mut items = self.items.take().unwrap_or(vec![]);
let mut items = self.items.take().unwrap_or_default();
let mut index = None;
// get the index
for i in 0..items.len() {
for (i, _) in items.iter().enumerate() {
if items[i].uid == Some(uid.clone()) {
index = Some(i);
break;
@@ -267,19 +269,19 @@ impl IProfiles {
}
if let Some(index) = index {
items.remove(index).file.map(|file| {
if let Some(file) = items.remove(index).file {
let _ = dirs::app_profiles_dir().map(|path| {
let path = path.join(file);
if path.exists() {
let _ = fs::remove_file(path);
}
});
});
}
}
// delete the original uid
if current == uid {
self.current = match items.len() > 0 {
self.current = match !items.is_empty() {
true => items[0].uid.clone(),
false => None,
};
@@ -299,7 +301,7 @@ impl IProfiles {
Some(file) => dirs::app_profiles_dir()?.join(file),
None => bail!("failed to get the file field"),
};
return Ok(help::read_merge_mapping(&file_path)?);
return help::read_merge_mapping(&file_path);
}
bail!("failed to find the current profile \"uid:{current}\"");
}

View File

@@ -19,10 +19,6 @@ pub struct IVerge {
/// `light` or `dark` or `system`
pub theme_mode: Option<String>,
/// enable blur mode
/// maybe be able to set the alpha
pub theme_blur: Option<bool>,
/// tray click event
pub tray_event: Option<String>,
@@ -57,6 +53,9 @@ pub struct IVerge {
/// set system proxy bypass
pub system_proxy_bypass: Option<String>,
/// set system proxy method
pub system_proxy_registry_mode: Option<bool>,
/// proxy guard duration
pub proxy_guard_duration: Option<u64>,
@@ -140,12 +139,12 @@ impl IVerge {
env_type: Some("bash".into()),
#[cfg(target_os = "windows")]
env_type: Some("powershell".into()),
theme_blur: Some(false),
traffic_graph: Some(true),
enable_memory_usage: Some(true),
enable_auto_launch: Some(false),
enable_silent_start: Some(false),
enable_system_proxy: Some(false),
system_proxy_registry_mode: Some(false),
enable_random_port: Some(false),
verge_mixed_port: Some(7897),
enable_proxy_guard: Some(false),
@@ -177,7 +176,6 @@ impl IVerge {
patch!(app_log_level);
patch!(language);
patch!(theme_mode);
patch!(theme_blur);
patch!(tray_event);
patch!(env_type);
patch!(traffic_graph);
@@ -192,6 +190,7 @@ impl IVerge {
patch!(enable_system_proxy);
patch!(enable_proxy_guard);
patch!(system_proxy_bypass);
patch!(system_proxy_registry_mode);
patch!(proxy_guard_duration);
patch!(theme_setting);

View File

@@ -83,12 +83,12 @@ fn clash_client_info() -> Result<(String, HeaderMap)> {
/// 缩短clash的日志
pub fn parse_log(log: String) -> String {
if log.starts_with("time=") && log.len() > 33 {
return (&log[33..]).to_owned();
return (log[33..]).to_owned();
}
if log.len() > 9 {
return (&log[9..]).to_owned();
return (log[9..]).to_owned();
}
return log;
log
}
/// 缩短clash -t的错误输出
@@ -105,7 +105,7 @@ pub fn parse_check_output(log: String) -> String {
};
if mr > m {
return (&log[e..mr]).to_owned();
return (log[e..mr]).to_owned();
}
}
@@ -113,7 +113,7 @@ pub fn parse_check_output(log: String) -> String {
let r = log.find("path=").or(Some(log.len()));
if let (Some(l), Some(r)) = (l, r) {
return (&log[(l + 6)..(r - 1)]).to_owned();
return (log[(l + 6)..(r - 1)]).to_owned();
}
log

View File

@@ -35,12 +35,12 @@ impl CoreManager {
.map(|pid| {
let mut system = System::new();
system.refresh_all();
system.process(Pid::from_u32(pid)).map(|proc| {
if let Some(proc) = system.process(Pid::from_u32(pid)) {
if proc.name().contains("clash") {
log::debug!(target: "app", "kill old clash process");
proc.kill();
}
});
}
});
tauri::async_runtime::spawn(async {
@@ -68,7 +68,7 @@ impl CoreManager {
if !output.status.success() {
let error = clash_api::parse_check_output(output.stdout.clone());
let error = match error.len() > 0 {
let error = match !error.is_empty() {
true => error,
false => output.stdout.clone(),
};
@@ -110,7 +110,7 @@ impl CoreManager {
use super::win_service;
// 服务模式
let enable = { Config::verge().latest().enable_service_mode.clone() };
let enable = { Config::verge().latest().enable_service_mode };
let enable = enable.unwrap_or(false);
*self.use_service_mode.lock() = enable;

View File

@@ -28,7 +28,7 @@ impl Handle {
self.app_handle
.lock()
.as_ref()
.map_or(None, |a| a.get_window("main"))
.and_then(|a| a.get_window("main"))
}
pub fn refresh_clash() {

View File

@@ -65,17 +65,17 @@ impl Hotkey {
}
let f = match func.trim() {
"open_dashboard" => || feat::open_dashboard(),
"open_dashboard" => feat::open_dashboard,
"clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()),
"clash_mode_script" => || feat::change_clash_mode("script".into()),
"toggle_system_proxy" => || feat::toggle_system_proxy(),
"enable_system_proxy" => || feat::enable_system_proxy(),
"disable_system_proxy" => || feat::disable_system_proxy(),
"toggle_tun_mode" => || feat::toggle_tun_mode(),
"enable_tun_mode" => || feat::enable_tun_mode(),
"disable_tun_mode" => || feat::disable_tun_mode(),
"toggle_system_proxy" => feat::toggle_system_proxy,
"enable_system_proxy" => feat::enable_system_proxy,
"disable_system_proxy" => feat::disable_system_proxy,
"toggle_tun_mode" => feat::toggle_tun_mode,
"enable_tun_mode" => feat::enable_tun_mode,
"disable_tun_mode" => feat::disable_tun_mode,
_ => bail!("invalid function \"{func}\""),
};
@@ -86,7 +86,7 @@ impl Hotkey {
}
fn unregister(&self, hotkey: &str) -> Result<()> {
self.get_manager()?.unregister(&hotkey)?;
self.get_manager()?.unregister(hotkey)?;
log::info!(target: "app", "unregister hotkey {hotkey}");
Ok(())
}
@@ -110,7 +110,7 @@ impl Hotkey {
Ok(())
}
fn get_map_from_vec<'a>(hotkeys: &'a Vec<String>) -> HashMap<&'a str, &'a str> {
fn get_map_from_vec(hotkeys: &Vec<String>) -> HashMap<&str, &str> {
let mut map = HashMap::new();
hotkeys.iter().for_each(|hotkey| {

View File

@@ -53,11 +53,17 @@ impl Sysopt {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
let registry_mode = {
let verge = Config::verge();
let verge = verge.latest();
verge.system_proxy_registry_mode.unwrap_or(false)
};
let current = Sysproxy {
enable,
host: String::from("127.0.0.1"),
@@ -66,8 +72,16 @@ impl Sysopt {
};
if enable {
let old = Sysproxy::get_system_proxy().map_or(None, |p| Some(p));
current.set_system_proxy()?;
let old = Sysproxy::get_system_proxy().ok();
if registry_mode {
#[cfg(windows)]
current.set_system_proxy_with_registry()?;
#[cfg(not(windows))]
current.set_system_proxy()?;
} else {
current.set_system_proxy()?;
}
*self.old_sysproxy.lock() = old;
*self.cur_sysproxy.lock() = Some(current);
@@ -93,16 +107,30 @@ impl Sysopt {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_system_proxy.unwrap_or(false),
verge.system_proxy_bypass.clone(),
)
};
let registry_mode = {
let verge = Config::verge();
let verge = verge.latest();
verge.system_proxy_registry_mode.unwrap_or(false)
};
let mut sysproxy = cur_sysproxy.take().unwrap();
sysproxy.enable = enable;
sysproxy.bypass = bypass.unwrap_or(DEFAULT_BYPASS.into());
sysproxy.set_system_proxy()?;
if registry_mode {
#[cfg(windows)]
sysproxy.set_system_proxy_with_registry()?;
#[cfg(not(windows))]
sysproxy.set_system_proxy()?;
} else {
sysproxy.set_system_proxy()?;
}
*cur_sysproxy = Some(sysproxy);
Ok(())
@@ -112,7 +140,11 @@ impl Sysopt {
pub fn reset_sysproxy(&self) -> Result<()> {
let mut cur_sysproxy = self.cur_sysproxy.lock();
let mut old_sysproxy = self.old_sysproxy.lock();
let registry_mode = {
let verge = Config::verge();
let verge = verge.latest();
verge.system_proxy_registry_mode.unwrap_or(false)
};
let cur_sysproxy = cur_sysproxy.take();
if let Some(mut old) = old_sysproxy.take() {
@@ -127,12 +159,26 @@ impl Sysopt {
log::info!(target: "app", "reset proxy to the original proxy");
}
old.set_system_proxy()?;
if registry_mode {
#[cfg(windows)]
old.set_system_proxy_with_registry()?;
#[cfg(not(windows))]
old.set_system_proxy()?;
} else {
old.set_system_proxy()?;
}
} else if let Some(mut cur @ Sysproxy { enable: true, .. }) = cur_sysproxy {
// 没有原代理就按现在的代理设置disable即可
log::info!(target: "app", "reset proxy by disabling the current proxy");
cur.enable = false;
cur.set_system_proxy()?;
if registry_mode {
#[cfg(windows)]
cur.set_system_proxy_with_registry()?;
#[cfg(not(windows))]
cur.set_system_proxy()?;
} else {
cur.set_system_proxy()?;
}
} else {
log::info!(target: "app", "reset proxy with no action");
}
@@ -142,7 +188,7 @@ impl Sysopt {
/// init the auto launch
pub fn init_launch(&self) -> Result<()> {
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = { Config::verge().latest().enable_auto_launch };
let enable = enable.unwrap_or(false);
let app_exe = current_exe()?;
@@ -233,7 +279,7 @@ impl Sysopt {
drop(auto_launch);
return self.init_launch();
}
let enable = { Config::verge().latest().enable_auto_launch.clone() };
let enable = { Config::verge().latest().enable_auto_launch };
let enable = enable.unwrap_or(false);
let auto_launch = auto_launch.as_ref().unwrap();
@@ -251,7 +297,11 @@ impl Sysopt {
use tokio::time::{sleep, Duration};
let guard_state = self.guard_state.clone();
let registry_mode = {
let verge = Config::verge();
let verge = verge.latest();
verge.system_proxy_registry_mode.unwrap_or(false)
};
tauri::async_runtime::spawn(async move {
// if it is running, exit
let mut state = guard_state.lock().await;
@@ -271,9 +321,9 @@ impl Sysopt {
let verge = Config::verge();
let verge = verge.latest();
(
verge.enable_system_proxy.clone().unwrap_or(false),
verge.enable_proxy_guard.clone().unwrap_or(false),
verge.proxy_guard_duration.clone().unwrap_or(10),
verge.enable_system_proxy.unwrap_or(false),
verge.enable_proxy_guard.unwrap_or(false),
verge.proxy_guard_duration.unwrap_or(10),
verge.system_proxy_bypass.clone(),
)
};
@@ -301,8 +351,14 @@ impl Sysopt {
port,
bypass: bypass.unwrap_or(DEFAULT_BYPASS.into()),
};
log_err!(sysproxy.set_system_proxy());
if registry_mode {
#[cfg(windows)]
log_err!(sysproxy.set_system_proxy_with_registry());
#[cfg(not(windows))]
log_err!(sysproxy.set_system_proxy());
} else {
log_err!(sysproxy.set_system_proxy());
}
}
let mut state = guard_state.lock().await;

View File

@@ -40,7 +40,7 @@ impl Timer {
let timer_map = self.timer_map.lock();
let delay_timer = self.delay_timer.lock();
Config::profiles().latest().get_items().map(|items| {
if let Some(items) = Config::profiles().latest().get_items() {
items
.iter()
.filter_map(|item| {
@@ -61,7 +61,7 @@ impl Timer {
}
}
})
});
}
Ok(())
}

View File

@@ -114,9 +114,9 @@ pub fn use_sort(config: Mapping, enable_filter: bool) -> Mapping {
.chain(DEFAULT_FIELDS)
.for_each(|key| {
let key = Value::from(key);
config.get(&key).map(|value| {
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
});
}
});
if !enable_filter {
@@ -134,9 +134,9 @@ pub fn use_sort(config: Mapping, enable_filter: bool) -> Mapping {
config_keys.difference(&supported_keys).for_each(|&key| {
let key = Value::from(key);
config.get(&key).map(|value| {
if let Some(value) = config.get(&key) {
ret.insert(key, value.clone());
});
}
});
}
@@ -150,7 +150,7 @@ pub fn use_keys(config: &Mapping) -> Vec<String> {
.map(|s| {
let mut s = s.to_string();
s.make_ascii_lowercase();
return s;
s
})
.collect()
}

View File

@@ -27,9 +27,9 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let verge = verge.latest();
(
verge.clash_core.clone(),
verge.enable_tun_mode.clone().unwrap_or(false),
verge.enable_builtin_enhanced.clone().unwrap_or(true),
verge.enable_clash_fields.clone().unwrap_or(true),
verge.enable_tun_mode.unwrap_or(false),
verge.enable_builtin_enhanced.unwrap_or(true),
verge.enable_clash_fields.unwrap_or(true),
)
};
@@ -38,18 +38,18 @@ pub fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
let profiles = Config::profiles();
let profiles = profiles.latest();
let current = profiles.current_mapping().unwrap_or(Mapping::new());
let current = profiles.current_mapping().unwrap_or_default();
let chain = match profiles.chain.as_ref() {
Some(chain) => chain
.iter()
.filter_map(|uid| profiles.get_item(uid).ok())
.filter_map(|item| <Option<ChainItem>>::from(item))
.filter_map(<Option<ChainItem>>::from)
.collect::<Vec<ChainItem>>(),
None => vec![],
};
let valid = profiles.valid.clone().unwrap_or(vec![]);
let valid = profiles.valid.clone().unwrap_or_default();
(current, chain, valid)
};

View File

@@ -47,7 +47,7 @@ pub fn use_script(script: String, config: Mapping) -> Result<(Mapping, Vec<(Stri
if result == "\"\"" {
anyhow::bail!("main function should return object");
}
return Ok(serde_json::from_str::<Mapping>(result.as_str())?);
Ok(serde_json::from_str::<Mapping>(result.as_str())?)
});
let mut out = outputs.lock().unwrap();

View File

@@ -62,7 +62,7 @@ pub fn change_clash_mode(mode: String) {
// 切换系统代理
pub fn toggle_system_proxy() {
let enable = Config::verge().draft().enable_system_proxy.clone();
let enable = Config::verge().draft().enable_system_proxy;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
@@ -110,7 +110,7 @@ pub fn disable_system_proxy() {
// 切换tun模式
pub fn toggle_tun_mode() {
let enable = Config::verge().data().enable_tun_mode.clone();
let enable = Config::verge().data().enable_tun_mode;
let enable = enable.unwrap_or(false);
tauri::async_runtime::spawn(async move {
@@ -164,14 +164,14 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
let mixed_port = patch.get("mixed-port");
let enable_random_port = Config::verge().latest().enable_random_port.unwrap_or(false);
if mixed_port.is_some() && !enable_random_port {
let changed = mixed_port.clone().unwrap()
let changed = mixed_port.unwrap()
!= Config::verge()
.latest()
.verge_mixed_port
.unwrap_or(Config::clash().data().get_mixed_port());
// 检查端口占用
if changed {
if let Some(port) = mixed_port.clone().unwrap().as_u64() {
if let Some(port) = mixed_port.unwrap().as_u64() {
if !port_scanner::local_port_available(port as u16) {
Config::clash().discard();
bail!("port already in use");

View File

@@ -25,7 +25,10 @@ fn main() -> std::io::Result<()> {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.system_tray(SystemTray::new())
.setup(|app| Ok(resolve::resolve_setup(app)))
.setup(|app| {
resolve::resolve_setup(app);
Ok(())
})
.on_system_tray_event(core::tray::Tray::on_system_tray_event)
.invoke_handler(tauri::generate_handler![
// common
@@ -133,13 +136,13 @@ fn main() -> std::io::Result<()> {
if label == "main" {
match event {
tauri::WindowEvent::Destroyed => {
let _ = resolve::save_window_size_position(&app_handle, true);
let _ = resolve::save_window_size_position(app_handle, true);
}
tauri::WindowEvent::CloseRequested { .. } => {
let _ = resolve::save_window_size_position(&app_handle, true);
let _ = resolve::save_window_size_position(app_handle, true);
}
tauri::WindowEvent::Moved(_) | tauri::WindowEvent::Resized(_) => {
let _ = resolve::save_window_size_position(&app_handle, false);
let _ = resolve::save_window_size_position(app_handle, false);
}
_ => {}
}

View File

@@ -14,7 +14,7 @@ pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
bail!("file not found \"{}\"", path.display());
}
let yaml_str = fs::read_to_string(&path)
let yaml_str = fs::read_to_string(path)
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
serde_yaml::from_str::<T>(&yaml_str).with_context(|| {
@@ -89,11 +89,11 @@ pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> {
let code = "code";
let _ = match Program::from_str(code) {
Ok(code) => open(&app.shell_scope(), &path.to_string_lossy(), Some(code)),
Ok(code) => open(&app.shell_scope(), path.to_string_lossy(), Some(code)),
Err(err) => {
log::error!(target: "app", "Can't find VScode `{err}`");
// default open
open(&app.shell_scope(), &path.to_string_lossy(), None)
open(&app.shell_scope(), path.to_string_lossy(), None)
}
};

View File

@@ -79,7 +79,7 @@ pub fn delete_log() -> Result<()> {
let auto_log_clean = {
let verge = Config::verge();
let verge = verge.data();
verge.auto_log_clean.clone().unwrap_or(0)
verge.auto_log_clean.unwrap_or(0)
};
let day = match auto_log_clean {
@@ -130,10 +130,8 @@ pub fn delete_log() -> Result<()> {
Ok(())
};
for file in fs::read_dir(&log_dir)? {
if let Ok(file) = file {
let _ = process_file(file);
}
for file in fs::read_dir(&log_dir)?.flatten() {
let _ = process_file(file);
}
Ok(())
}
@@ -310,14 +308,14 @@ pub fn init_scheme() -> Result<()> {
let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?;
let app_exe = app_exe.to_string_lossy().to_owned();
let app_exe = app_exe.to_string_lossy().into_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}"))?;
default_icon.set_value("", &app_exe)?;
let (command, _) = hkcu.create_subkey("Software\\Classes\\Clash\\Shell\\Open\\Command")?;
command.set_value("", &format!("{app_exe} \"%1\""))?;
@@ -325,6 +323,17 @@ pub fn init_scheme() -> Result<()> {
}
#[cfg(target_os = "linux")]
pub fn init_scheme() -> Result<()> {
let output = std::process::Command::new("xdg-mime")
.arg("default")
.arg("clash-verge.desktop")
.arg("x-scheme-handler/clash")
.output()?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"failed to set clash scheme, {}",
String::from_utf8_lossy(&output.stderr)
));
}
Ok(())
}
#[cfg(target_os = "macos")]

View File

@@ -86,7 +86,7 @@ pub fn resolve_setup(app: &mut App) {
log::trace!("init system tray");
log_err!(tray::Tray::update_systray(&app.app_handle()));
let silent_start = { Config::verge().data().enable_silent_start.clone() };
let silent_start = { Config::verge().data().enable_silent_start };
if !silent_start.unwrap_or(false) {
create_window(&app.app_handle());
}
@@ -240,15 +240,17 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
}
pub async fn resolve_scheme(param: String) {
let url = param.trim_start_matches("clash://install-config/?url=");
let url = param
.trim_start_matches("clash://install-config/?url=")
.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) {
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
if Config::profiles().data().append_item(item).is_ok() {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile success")

View File

@@ -1,7 +1,7 @@
{
"package": {
"productName": "Clash Verge",
"version": "1.4.6"
"version": "1.4.8"
},
"build": {
"distDir": "../dist",

View File

@@ -6,7 +6,8 @@
"bundle": {
"targets": ["deb", "appimage", "updater"],
"deb": {
"depends": ["openssl"]
"depends": ["openssl"],
"desktopTemplate": "./clash-verge.desktop"
}
}
}

View File

@@ -3,6 +3,7 @@ import { useMemo, useState } from "react";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { truncateStr } from "@/utils/truncate-str";
import parseTraffic from "@/utils/parse-traffic";
import { sortWithUnit, sortStringTime } from "@/utils/custom-comparator";
interface Props {
connections: IConnectionsItem[];
@@ -24,6 +25,7 @@ export const ConnectionTable = (props: Props) => {
width: 88,
align: "right",
headerAlign: "right",
sortComparator: sortWithUnit,
},
{
field: "upload",
@@ -31,6 +33,7 @@ export const ConnectionTable = (props: Props) => {
width: 88,
align: "right",
headerAlign: "right",
sortComparator: sortWithUnit,
},
{
field: "dlSpeed",
@@ -38,6 +41,7 @@ export const ConnectionTable = (props: Props) => {
width: 88,
align: "right",
headerAlign: "right",
sortComparator: sortWithUnit,
},
{
field: "ulSpeed",
@@ -45,6 +49,7 @@ export const ConnectionTable = (props: Props) => {
width: 88,
align: "right",
headerAlign: "right",
sortComparator: sortWithUnit,
},
{ field: "chains", headerName: "Chains", flex: 360, minWidth: 360 },
{ field: "rule", headerName: "Rule", flex: 300, minWidth: 250 },
@@ -56,6 +61,7 @@ export const ConnectionTable = (props: Props) => {
minWidth: 100,
align: "right",
headerAlign: "right",
sortComparator: sortStringTime,
},
{ field: "source", headerName: "Source", flex: 200, minWidth: 130 },
{
@@ -72,7 +78,6 @@ export const ConnectionTable = (props: Props) => {
const { metadata, rulePayload } = each;
const chains = [...each.chains].reverse().join(" / ");
const rule = rulePayload ? `${each.rule}(${rulePayload})` : each.rule;
return {
id: each.id,
host: metadata.host

View File

@@ -98,6 +98,8 @@ export const ProxyItemMini = (props: Props) => {
)}
<TypeBox component="span">{proxy.type}</TypeBox>
{proxy.udp && <TypeBox component="span">UDP</TypeBox>}
{proxy.xudp && <TypeBox component="span">XUDP</TypeBox>}
{proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
</Box>
)}
</Box>

View File

@@ -104,6 +104,10 @@ export const ProxyItem = (props: Props) => {
)}
{showType && <TypeBox component="span">{proxy.type}</TypeBox>}
{showType && proxy.udp && <TypeBox component="span">UDP</TypeBox>}
{showType && proxy.xudp && (
<TypeBox component="span">XUDP</TypeBox>
)}
{showType && proxy.tfo && <TypeBox component="span">TFO</TypeBox>}
</>
}
/>

View File

@@ -31,7 +31,7 @@ export const ProxyRender = (props: RenderProps) => {
props;
const { type, group, headState, proxy, proxyCol } = item;
if (type === 0) {
if (type === 0 && !group.hidden) {
return (
<ListItemButton
dense
@@ -61,7 +61,7 @@ export const ProxyRender = (props: RenderProps) => {
);
}
if (type === 1) {
if (type === 1 && !group.hidden) {
return (
<ProxyHead
sx={{ pl: indent ? 4.5 : 2.5, pr: 3, mt: indent ? 1 : 0.5, mb: 1 }}
@@ -74,7 +74,7 @@ export const ProxyRender = (props: RenderProps) => {
);
}
if (type === 2) {
if (type === 2 && !group.hidden) {
return (
<ProxyItem
groupName={group.name}
@@ -87,7 +87,7 @@ export const ProxyRender = (props: RenderProps) => {
);
}
if (type === 3) {
if (type === 3 && !group.hidden) {
return (
<Box
sx={{
@@ -105,7 +105,7 @@ export const ProxyRender = (props: RenderProps) => {
);
}
if (type === 4) {
if (type === 4 && !group.hidden) {
return (
<Box
sx={{

View File

@@ -36,19 +36,6 @@ export const LayoutViewer = forwardRef<DialogRef>((props, ref) => {
onCancel={() => setOpen(false)}
>
<List>
<SettingItem label={t("Theme Blur")}>
<GuardState
value={verge?.theme_blur ?? false}
valueProps="checked"
onCatch={onError}
onFormat={onSwitchFormat}
onChange={(e) => onChangeData({ theme_blur: e })}
onGuard={(e) => patchVerge({ theme_blur: e })}
>
<Switch edge="end" />
</GuardState>
</SettingItem>
<SettingItem label={t("Traffic Graph")}>
<GuardState
value={verge?.traffic_graph ?? true}

View File

@@ -11,11 +11,15 @@ import {
Switch,
TextField,
Typography,
Tooltip,
} from "@mui/material";
import getSystem from "@/utils/get-system";
import { useVerge } from "@/hooks/use-verge";
import { getSystemProxy } from "@/services/cmds";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
const OS = getSystem();
export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
const { t } = useTranslation();
@@ -31,12 +35,14 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
enable_proxy_guard,
system_proxy_bypass,
proxy_guard_duration,
system_proxy_registry_mode,
} = verge ?? {};
const [value, setValue] = useState({
guard: enable_proxy_guard,
bypass: system_proxy_bypass,
duration: proxy_guard_duration ?? 10,
registryMode: system_proxy_registry_mode,
});
useImperativeHandle(ref, () => ({
@@ -46,6 +52,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
guard: enable_proxy_guard,
bypass: system_proxy_bypass,
duration: proxy_guard_duration ?? 10,
registryMode: system_proxy_registry_mode,
});
getSystemProxy().then((p) => setSysproxy(p));
},
@@ -69,6 +76,9 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
if (value.bypass !== system_proxy_bypass) {
patch.system_proxy_bypass = value.bypass;
}
if (value.registryMode !== system_proxy_registry_mode) {
patch.system_proxy_registry_mode = value.registryMode;
}
try {
await patchVerge(patch);
@@ -82,7 +92,7 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
<BaseDialog
open={open}
title={t("System Proxy Setting")}
contentSx={{ width: 450, maxHeight: 300 }}
contentSx={{ width: 450, maxHeight: 500 }}
okBtn={t("Save")}
cancelBtn={t("Cancel")}
onClose={() => setOpen(false)}
@@ -134,6 +144,27 @@ export const SysproxyViewer = forwardRef<DialogRef>((props, ref) => {
}
/>
</ListItem>
{OS === "windows" && (
<Tooltip
title={
enabled
? t("Please disable the system proxy")
: t("Using the registry instead of Windows API")
}
>
<ListItem sx={{ padding: "5px 2px" }}>
<ListItemText primary={t("Use Registry")} />
<Switch
edge="end"
disabled={enabled}
checked={value.registryMode}
onChange={(_, e) =>
setValue((v) => ({ ...v, registryMode: e }))
}
/>
</ListItem>
</Tooltip>
)}
</List>
<Box sx={{ mt: 2.5 }}>

View File

@@ -10,6 +10,7 @@ import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
import { BaseDialog, DialogRef, Notice } from "@/components/base";
import { atomUpdateState } from "@/services/states";
import { listen, Event, UnlistenFn } from "@tauri-apps/api/event";
import { portableFlag } from "@/pages/_layout";
const UpdateLog = styled(Box)(() => ({
"h1,h2,h3,ul,ol,p": { margin: "0.5em 0", color: "inherit" },
@@ -46,6 +47,10 @@ export const UpdateViewer = forwardRef<DialogRef>((props, ref) => {
}, [updateInfo]);
const onUpdate = useLockFn(async () => {
if (portableFlag) {
Notice.error(t("Portable Updater Error"));
return;
}
if (updateState) return;
setUpdateState(true);
if (eventListener !== null) {

View File

@@ -84,12 +84,12 @@
"Proxy Guard": "Proxy Guard",
"Guard Duration": "Guard Duration",
"Proxy Bypass": "Proxy Bypass",
"Use Registry": "Use Registry",
"Enable status": "Enable status",
"Server Addr": "Server Addr",
"Bypass": "Bypass",
"Current System Proxy": "Current System Proxy",
"Theme Mode": "Theme Mode",
"Theme Blur": "Theme Blur",
"Tray Click Event": "Tray Click Event",
"Copy Env Type": "Copy Env Type",
"Show Main Window": "Show Main Window",
@@ -145,5 +145,9 @@
"Never Clean": "Never Clean",
"Retain 7 Days": "Retain 7 Days",
"Retain 30 Days": "Retain 30 Days",
"Retain 90 Days": "Retain 90 Days"
"Retain 90 Days": "Retain 90 Days",
"Portable Updater Error": "The portable version does not support in-app updates. Please manually download and replace it",
"Please disable the system proxy": "Please disable the system proxy",
"Using the registry instead of Windows API": "Using the registry instead of Windows API"
}

View File

@@ -78,9 +78,9 @@
"Proxy Guard": "Защита прокси",
"Guard Duration": "Период защиты",
"Proxy Bypass": "Игнорирование прокси",
"Use Registry": "Использование реестра",
"Current System Proxy": "Текущий системный прокси",
"Theme Mode": "Режим темы",
"Theme Blur": "Размытие темы",
"Tray Click Event": "Событие щелчка в лотке",
"Copy Env Type": "Скопировать тип Env",
"Show Main Window": "Показать главное окно",
@@ -115,5 +115,9 @@
"disable_system_proxy": "Отключить системный прокси",
"toggle_tun_mode": "Переключить режим туннеля",
"enable_tun_mode": "Включить режим туннеля",
"disable_tun_mode": "Отключить режим туннеля"
"disable_tun_mode": "Отключить режим туннеля",
"Portable Updater Error": "Портативная версия не поддерживает обновление внутри приложения, пожалуйста, скачайте и замените вручную",
"Please disable the system proxy": "Пожалуйста, отключите системный прокси",
"Using the registry instead of Windows API": "Использование реестра вместо Windows API"
}

View File

@@ -84,12 +84,12 @@
"Proxy Guard": "系统代理守卫",
"Guard Duration": "代理守卫间隔",
"Proxy Bypass": "代理绕过",
"Use Registry": "使用注册表",
"Current System Proxy": "当前系统代理",
"Enable status": "开启状态:",
"Server Addr": "服务地址:",
"Bypass": "当前绕过:",
"Theme Mode": "主题模式",
"Theme Blur": "背景模糊",
"Tray Click Event": "托盘点击事件",
"Copy Env Type": "复制环境变量类型",
"Show Main Window": "显示主窗口",
@@ -145,5 +145,9 @@
"Never Clean": "不清理",
"Retain 7 Days": "保留7天",
"Retain 30 Days": "保留30天",
"Retain 90 Days": "保留90天"
"Retain 90 Days": "保留90天",
"Portable Updater Error": "便携版不支持应用内更新,请手动下载替换",
"Please disable the system proxy": "请先关闭系统代理",
"Using the registry instead of Windows API": "使用注册表替代Windows API"
}

View File

@@ -36,7 +36,7 @@ const Layout = () => {
const { theme } = useCustomTheme();
const { verge } = useVerge();
const { theme_blur, language } = verge || {};
const { language } = verge || {};
const location = useLocation();
@@ -116,7 +116,7 @@ const Layout = () => {
}}
sx={[
({ palette }) => ({
bgcolor: alpha(palette.background.paper, theme_blur ? 0.8 : 1),
bgcolor: palette.background.paper,
}),
]}
>

View File

@@ -24,6 +24,7 @@ import {
ConnectionDetail,
ConnectionDetailRef,
} from "@/components/connection/connection-detail";
import parseTraffic from "@/utils/parse-traffic";
const initConn = { uploadTotal: 0, downloadTotal: 0, connections: [] };
@@ -48,14 +49,20 @@ const ConnectionsPage = () => {
list.sort((a, b) => b.curDownload! - a.curDownload!),
};
const filterConn = useMemo(() => {
const [filterConn, download, upload] = useMemo(() => {
const orderFunc = orderOpts[curOrderOpt];
const connections = connData.connections.filter((conn) =>
let connections = connData.connections.filter((conn) =>
(conn.metadata.host || conn.metadata.destinationIP)?.includes(filterText)
);
if (orderFunc) return orderFunc(connections);
return connections;
if (orderFunc) connections = orderFunc(connections);
let download = 0;
let upload = 0;
connections.forEach((x) => {
download += x.download;
upload += x.upload;
});
return [connections, download, upload];
}, [connData, filterText, curOrderOpt]);
const { connect, disconnect } = useWebsocket(
@@ -119,6 +126,8 @@ const ConnectionsPage = () => {
contentStyle={{ height: "100%" }}
header={
<Box sx={{ mt: 1, display: "flex", alignItems: "center", gap: 2 }}>
<Box sx={{ mx: 1 }}>Download: {parseTraffic(download)}</Box>
<Box sx={{ mx: 1 }}>Upload: {parseTraffic(upload)}</Box>
<IconButton
color="inherit"
size="small"
@@ -184,7 +193,6 @@ const ConnectionsPage = () => {
placeholder={t("Filter conditions")}
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
sx={{ input: { py: 0.65, px: 1.25 } }}
/>
</Box>

View File

@@ -119,7 +119,14 @@ export const getProxies = async () => {
const generateItem = (name: string) => {
if (proxyRecord[name]) return proxyRecord[name];
if (providerMap[name]) return providerMap[name];
return { name, type: "unknown", udp: false, history: [] };
return {
name,
type: "unknown",
udp: false,
xudp: false,
tfo: false,
history: [],
};
};
const { GLOBAL: global, DIRECT: direct, REJECT: reject } = proxyRecord;

View File

@@ -44,12 +44,15 @@ interface IProxyItem {
name: string;
type: string;
udp: boolean;
xudp: boolean;
tfo: boolean;
history: {
time: string;
delay: number;
}[];
all?: string[];
now?: string;
hidden?: boolean;
provider?: string; // 记录是否来自provider
}
@@ -158,7 +161,6 @@ interface IVergeConfig {
env_type?: "bash" | "cmd" | "powershell" | string;
clash_core?: string;
theme_mode?: "light" | "dark" | "system";
theme_blur?: boolean;
traffic_graph?: boolean;
enable_memory_usage?: boolean;
enable_tun_mode?: boolean;
@@ -171,6 +173,7 @@ interface IVergeConfig {
enable_proxy_guard?: boolean;
proxy_guard_duration?: number;
system_proxy_bypass?: string;
system_proxy_registry_mode?: boolean;
web_ui_list?: string[];
hotkeys?: string[];
theme_setting?: {

View File

@@ -0,0 +1,38 @@
import { GridComparatorFn } from "@mui/x-data-grid";
const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const unitMap = new Map<string, number>();
unitMap.set("分钟前", 60);
unitMap.set("小时前", 60 * 60);
unitMap.set("天前", 60 * 60 * 24);
unitMap.set("个月前", 60 * 60 * 24 * 30);
unitMap.set("年前", 60 * 60 * 24 * 30 * 12);
export const sortWithUnit: GridComparatorFn<string> = (v1, v2) => {
const [ret1, unit1] = v1.split(" ");
const [ret2, unit2] = v2.split(" ");
let value1 =
parseFloat(ret1) *
Math.pow(1024, UNITS.indexOf(unit1.replace("/s", "").trim()));
let value2 =
parseFloat(ret2) *
Math.pow(1024, UNITS.indexOf(unit2.replace("/s", "").trim()));
return value1 - value2;
};
export const sortStringTime: GridComparatorFn<string> = (v1, v2) => {
if (v1 === "几秒前") {
return -1;
}
if (v2 === "几秒前") {
return 1;
}
const matches1 = v1.match(/[0-9]+/);
const num1 = matches1 !== null ? parseInt(matches1[0]) : 0;
const matches2 = v2.match(/[0-9]+/);
const num2 = matches2 !== null ? parseInt(matches2[0]) : 0;
const unit1 = unitMap.get(v1.replace(num1.toString(), "").trim()) || 0;
const unit2 = unitMap.get(v2.replace(num2.toString(), "").trim()) || 0;
return num1 * unit1 - num2 * unit2;
};