diff --git a/.stylelintrc.js b/.stylelintrc.js index 8405bc6df7..8e4e085fe4 100644 --- a/.stylelintrc.js +++ b/.stylelintrc.js @@ -13,6 +13,10 @@ module.exports = { // 'stylelint-config-prettier' ], rules: { + "selector-pseudo-class-no-unknown": [ + true, + { ignorePseudoClasses: ["global"] }, + ], "font-family-name-quotes": null, "font-family-no-missing-generic-family-keyword": null, "max-nesting-depth": [ @@ -24,7 +28,7 @@ module.exports = { "declaration-block-no-duplicate-properties": true, "no-duplicate-selectors": true, "no-descending-specificity": null, - "selector-class-pattern": "^([a-z][a-z0-9]*)((-|__)[a-z0-9]+)*$", + "selector-class-pattern": null, "value-no-vendor-prefix": [true, { ignoreValues: ["box"] }], "at-rule-no-unknown": [ true, diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 660e816944..3ab05ea1a6 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -281,9 +281,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.5.0" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" +checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" [[package]] name = "async-trait" @@ -2043,11 +2043,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2126,9 +2126,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -2141,7 +2141,7 @@ dependencies = [ "httpdate", "itoa 1.0.10", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -4155,14 +4155,12 @@ checksum = "e60ef3b82994702bbe4e134d98aadca4b49ed04440148985678d415c68127666" [[package]] name = "runas" -version = "1.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49535b7c73aec5596ae2c44a6d8a7a8f8592e5744564c327fd4846750413d921" +checksum = "ed87390fefd18965ff20baae5aeb9913bcf82d2b59dc04c0f6d8f17f7be56ff2" dependencies = [ - "libc", - "security-framework-sys", + "cc", "which", - "windows-sys 0.48.0", ] [[package]] @@ -5305,18 +5303,18 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", diff --git a/backend/tauri/Cargo.toml b/backend/tauri/Cargo.toml index 7fcb992cc2..2a711bffc9 100644 --- a/backend/tauri/Cargo.toml +++ b/backend/tauri/Cargo.toml @@ -64,7 +64,7 @@ thiserror = { workspace = true, version = "1.0" } simd-json = "0.13.4" [target.'cfg(windows)'.dependencies] -runas = "=1.1.0" +runas = "=1.0.0" # blocked by https://github.com/mitsuhiko/rust-runas/issues/13 deelevate = "0.2.0" winreg = { version = "0.50", features = ["transactions"] } windows-sys = { version = "0.48", features = [ diff --git a/backend/tauri/src/cmds.rs b/backend/tauri/src/cmds.rs index 3240c16a44..e9648c4de8 100644 --- a/backend/tauri/src/cmds.rs +++ b/backend/tauri/src/cmds.rs @@ -299,6 +299,17 @@ pub async fn update_core(core_type: ClashCore) -> CmdResult { ) } +#[tauri::command] +pub async fn clash_api_get_proxy_delay( + name: String, + url: Option, +) -> CmdResult { + match clash_api::get_proxy_delay(name, url).await { + Ok(res) => Ok(res), + Err(err) => Err(format!("{}", err.to_string())), + } +} + #[cfg(windows)] pub mod uwp { use super::*; diff --git a/backend/tauri/src/config/clash.rs b/backend/tauri/src/config/clash.rs index e146b248b7..9ccc52b961 100644 --- a/backend/tauri/src/config/clash.rs +++ b/backend/tauri/src/config/clash.rs @@ -129,6 +129,26 @@ impl IClashTemp { Err(_) => "127.0.0.1:9090".into(), } } + + pub fn get_tun_device_ip(&self) -> String { + let config = &self.0; + + let ip = config + .get("dns") + .and_then(|value| match value { + Value::Mapping(val_map) => Some(val_map.get("fake-ip-range").and_then( + |fake_ip_range| match fake_ip_range { + Value::String(ip_range_val) => Some(ip_range_val.replace("1/16", "2")), + _ => None, + }, + )), + _ => None, + }) + // 默认IP + .unwrap_or(Some("198.18.0.2".to_string())); + + ip.unwrap() + } } #[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] diff --git a/backend/tauri/src/core/clash_api.rs b/backend/tauri/src/core/clash_api.rs index 4714371bbc..28255f2c6f 100644 --- a/backend/tauri/src/core/clash_api.rs +++ b/backend/tauri/src/core/clash_api.rs @@ -1,6 +1,7 @@ use crate::config::Config; use anyhow::{bail, Result}; use reqwest::header::HeaderMap; +use serde::{Deserialize, Serialize}; use serde_yaml::Mapping; use std::collections::HashMap; @@ -19,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}\"") } } @@ -36,6 +37,31 @@ pub async fn patch_configs(config: &Mapping) -> Result<()> { Ok(()) } +#[derive(Default, Debug, Clone, Deserialize, Serialize)] +pub struct DelayRes { + delay: u64, +} + +/// GET /proxies/{name}/delay +/// 获取代理延迟 +pub async fn get_proxy_delay(name: String, test_url: Option) -> Result { + let (url, headers) = clash_client_info()?; + let url = format!("{url}/proxies/{name}/delay"); + let default_url = "http://www.gstatic.com/generate_204"; + let test_url = test_url + .map(|s| if s.is_empty() { default_url.into() } else { s }) + .unwrap_or(default_url.into()); + + let client = reqwest::ClientBuilder::new().no_proxy().build()?; + let builder = client + .get(&url) + .headers(headers) + .query(&[("timeout", "10000"), ("url", &test_url)]); + let response = builder.send().await?; + + Ok(response.json::().await?) +} + /// 根据clash info获取clash服务地址和请求头 fn clash_client_info() -> Result<(String, HeaderMap)> { let client = { Config::clash().data().get_client_info() }; @@ -56,12 +82,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的错误输出 @@ -78,7 +104,7 @@ pub fn parse_check_output(log: String) -> String { }; if mr > m { - return (&log[e..mr]).to_owned(); + return log[e..mr].to_owned(); } } @@ -86,7 +112,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 diff --git a/backend/tauri/src/core/core.rs b/backend/tauri/src/core/core.rs index 5caa24693f..5476317825 100644 --- a/backend/tauri/src/core/core.rs +++ b/backend/tauri/src/core/core.rs @@ -104,7 +104,31 @@ impl CoreManager { if should_kill { sleep(Duration::from_millis(500)).await; } + #[cfg(target_os = "macos")] + { + let enable_tun = Config::verge().latest().enable_tun_mode.clone(); + let enable_tun = enable_tun.unwrap_or(false); + + if enable_tun { + log::debug!(target: "app", "try to set system dns"); + match (|| async { + let tun_device_ip = Config::clash().clone().latest().get_tun_device_ip(); + // 执行 networksetup -setdnsservers Wi-Fi $tun_device_ip + Command::new("networksetup") + .args(["-setdnsservers", "Wi-Fi", tun_device_ip.as_str()]) + .output() + })() + .await + { + Ok(_) => return Ok(()), + Err(err) => { + // 修改这个值,免得stop出错 + log::error!(target: "app", "{err}"); + } + } + } + } #[cfg(target_os = "windows")] { use super::win_service; @@ -253,6 +277,29 @@ impl CoreManager { return Ok(()); } + #[cfg(target_os = "macos")] + { + let enable_tun = Config::verge().latest().enable_tun_mode.clone(); + let enable_tun = enable_tun.unwrap_or(false); + + if enable_tun { + log::debug!(target: "app", "try to set system dns"); + + match (|| { + // 执行 networksetup -setdnsservers Wi-Fi "Empty" + Command::new("networksetup") + .args(["-setdnsservers", "Wi-Fi", "Empty"]) + .output() + })() { + Ok(_) => return Ok(()), + Err(err) => { + // 修改这个值,免得stop出错 + *self.use_service_mode.lock() = false; + log::error!(target: "app", "{err}"); + } + } + } + } let mut sidecar = self.sidecar.lock(); if let Some(child) = sidecar.take() { log::debug!(target: "app", "stop the core by sidecar"); diff --git a/backend/tauri/src/main.rs b/backend/tauri/src/main.rs index 702f80bb0b..f8de7fa2a3 100644 --- a/backend/tauri/src/main.rs +++ b/backend/tauri/src/main.rs @@ -46,6 +46,7 @@ fn main() -> std::io::Result<()> { cmds::get_runtime_yaml, cmds::get_runtime_exists, cmds::get_runtime_logs, + cmds::clash_api_get_proxy_delay, cmds::uwp::invoke_uwp_tool, // updater cmds::fetch_latest_core_versions, diff --git a/src/components/base/base-dialog.module.scss b/src/components/base/base-dialog.module.scss new file mode 100644 index 0000000000..e0ca825020 --- /dev/null +++ b/src/components/base/base-dialog.module.scss @@ -0,0 +1,5 @@ +.basePageTransition { + :global(.MuiDialog-paper) { + max-height: calc(100vh - 64px); + } +} diff --git a/src/components/base/base-dialog.tsx b/src/components/base/base-dialog.tsx index dfe428e38c..55b8623f7b 100644 --- a/src/components/base/base-dialog.tsx +++ b/src/components/base/base-dialog.tsx @@ -1,3 +1,4 @@ +import { classNames } from "@/utils"; import { LoadingButton } from "@mui/lab"; import { Button, @@ -11,6 +12,8 @@ import { import { TransitionProps } from "@mui/material/transitions"; import { AnimatePresence, motion } from "framer-motion"; import React, { ReactNode } from "react"; +import styles from "./base-dialog.module.scss"; + interface Props { title: ReactNode; open: boolean; @@ -50,7 +53,6 @@ export function BaseDialog(props: Props) { return ( {inProp && ( { /// Get Proxy delay export const getProxyDelay = async (name: string, url?: string) => { const params = { - timeout: 5000, + timeout: 10000, url: url || "http://www.gstatic.com/generate_204", }; const instance = await getAxios(); diff --git a/src/services/cmds.ts b/src/services/cmds.ts index 861741d58b..067c0571f4 100644 --- a/src/services/cmds.ts +++ b/src/services/cmds.ts @@ -212,3 +212,8 @@ export async function updateCore( export async function collectLogs() { return invoke("collect_logs"); } + +export async function cmdGetProxyDelay(name: string, url?: string) { + name = encodeURIComponent(name); + return invoke<{ delay: number }>("clash_api_get_proxy_delay", { name, url }); +} diff --git a/src/services/delay.ts b/src/services/delay.ts index 91e4ab8985..b613f7e55c 100644 --- a/src/services/delay.ts +++ b/src/services/delay.ts @@ -1,4 +1,5 @@ import { getProxyDelay } from "./api"; +import { cmdGetProxyDelay } from "./cmds"; const hashKey = (name: string, group: string) => `${group ?? ""}::${name}`; @@ -74,7 +75,7 @@ class DelayManager { try { const url = this.getUrl(group); - const result = await getProxyDelay(name, url); + const result = await cmdGetProxyDelay(name, url); delay = result.delay; } catch { delay = 1e6; // error @@ -84,7 +85,7 @@ class DelayManager { return delay; } - async checkListDelay(nameList: string[], group: string, concurrency = 6) { + async checkListDelay(nameList: string[], group: string, concurrency = 36) { const names = nameList.filter(Boolean); // 设置正在延迟测试中 names.forEach((name) => this.setDelay(name, group, -2));