From 8339fabb17c3b49b2db9d7d86e8eb773c4766093 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Tue, 18 Nov 2025 15:48:48 +0800 Subject: [PATCH] feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval (#5510) * feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval * feat(sysinfo): add tauri-plugin-clash-verge-sysinfo for system information retrieval * fix(service): import Manager trait for app handle in linux_running_as_root function --- Cargo.lock | 14 +- Cargo.toml | 6 + Changelog.md | 1 + .../Cargo.toml | 19 +++ .../src/lib.rs | 150 ++++++++++++++++++ src-tauri/Cargo.toml | 5 +- src-tauri/src/cmd/network.rs | 9 +- src-tauri/src/cmd/system.rs | 48 +++--- src-tauri/src/core/manager/lifecycle.rs | 17 ++ src-tauri/src/core/service.rs | 13 +- src-tauri/src/lib.rs | 1 + src-tauri/src/module/mod.rs | 1 - src-tauri/src/module/sysinfo.rs | 69 -------- 13 files changed, 242 insertions(+), 111 deletions(-) create mode 100644 crates/tauri-plugin-clash-verge-sysinfo/Cargo.toml create mode 100644 crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs delete mode 100644 src-tauri/src/module/sysinfo.rs diff --git a/Cargo.lock b/Cargo.lock index 5b093dc7f..0817a4f63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1130,7 +1130,6 @@ dependencies = [ "futures", "gethostname", "getrandom 0.3.4", - "libc", "log", "nanoid", "network-interface", @@ -1150,11 +1149,11 @@ dependencies = [ "serde_yaml_ng", "smartstring", "sys-locale", - "sysinfo", "sysproxy", "tauri", "tauri-build", "tauri-plugin-autostart", + "tauri-plugin-clash-verge-sysinfo", "tauri-plugin-clipboard-manager", "tauri-plugin-deep-link", "tauri-plugin-devtools", @@ -7473,6 +7472,17 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "tauri-plugin-clash-verge-sysinfo" +version = "0.1.0" +dependencies = [ + "deelevate", + "libc", + "parking_lot", + "sysinfo", + "tauri", +] + [[package]] name = "tauri-plugin-clipboard-manager" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index 80beae005..56319eedb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/clash-verge-draft", "crates/clash-verge-logging", "crates/clash-verge-signal", + "crates/tauri-plugin-clash-verge-sysinfo", ] resolver = "2" @@ -44,6 +45,7 @@ strip = false clash-verge-draft = { path = "crates/clash-verge-draft" } clash-verge-logging = { path = "crates/clash-verge-logging" } clash-verge-signal = { path = "crates/clash-verge-signal" } +tauri-plugin-clash-verge-sysinfo = { path = "crates/tauri-plugin-clash-verge-sysinfo" } tauri = { version = "2.9.3" } parking_lot = { version = "0.12.5", features = [ "hardware-lock-elision", @@ -61,6 +63,10 @@ compact_str = { version = "0.9.0", features = ["serde"] } flexi_logger = "0.31.7" log = "0.4.28" +# *** For Windows platform only *** +deelevate = "0.2.0" +# ********************************* + [workspace.lints.clippy] correctness = { level = "deny", priority = -1 } suspicious = { level = "deny", priority = -1 } diff --git a/Changelog.md b/Changelog.md index 283fc612f..857b204ce 100644 --- a/Changelog.md +++ b/Changelog.md @@ -29,6 +29,7 @@ - i18n 支持 - 优化备份设置布局 - 优化流量图性能表现,实现动态 FPS 和窗口失焦自动暂停 +- 性能优化系统状态获取 diff --git a/crates/tauri-plugin-clash-verge-sysinfo/Cargo.toml b/crates/tauri-plugin-clash-verge-sysinfo/Cargo.toml new file mode 100644 index 000000000..282e90776 --- /dev/null +++ b/crates/tauri-plugin-clash-verge-sysinfo/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "tauri-plugin-clash-verge-sysinfo" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true + +[dependencies] +tauri = { workspace = true } +parking_lot = { workspace = true } +sysinfo = { version = "0.37.2", features = ["network", "system"] } + +[target.'cfg(not(windows))'.dependencies] +libc = "0.2.177" + +[target.'cfg(windows)'.dependencies] +deelevate = { workspace = true } + +[lints] +workspace = true diff --git a/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs b/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs new file mode 100644 index 000000000..0364c87e7 --- /dev/null +++ b/crates/tauri-plugin-clash-verge-sysinfo/src/lib.rs @@ -0,0 +1,150 @@ +use std::{ + fmt::{Debug, Display}, + time::Instant, +}; + +#[cfg(windows)] +use deelevate::{PrivilegeLevel, Token}; +use parking_lot::RwLock; +use sysinfo::{Networks, System}; +use tauri::{ + Manager as _, Runtime, + plugin::{Builder, TauriPlugin}, +}; + +pub struct SysInfo { + system_name: String, + system_version: String, + system_kernel_version: String, + system_arch: String, +} + +impl Default for SysInfo { + #[inline] + fn default() -> Self { + let system_name = System::name().unwrap_or_else(|| "Null".into()); + let system_version = System::long_os_version().unwrap_or_else(|| "Null".into()); + let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into()); + let system_arch = System::cpu_arch(); + Self { + system_name, + system_version, + system_kernel_version, + system_arch, + } + } +} + +pub struct AppInfo { + app_version: String, + app_core_mode: String, + pub app_startup_time: Instant, + pub app_is_admin: bool, +} + +impl Default for AppInfo { + #[inline] + fn default() -> Self { + let app_version = "0.0.0".into(); + let app_core_mode = "NotRunning".into(); + let app_is_admin = false; + let app_startup_time = Instant::now(); + Self { + app_version, + app_core_mode, + app_startup_time, + app_is_admin, + } + } +} + +#[derive(Default)] +pub struct Platform { + pub sysinfo: SysInfo, + pub appinfo: AppInfo, +} + +impl Debug for Platform { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Platform") + .field("system_name", &self.sysinfo.system_name) + .field("system_version", &self.sysinfo.system_version) + .field("system_kernel_version", &self.sysinfo.system_kernel_version) + .field("system_arch", &self.sysinfo.system_arch) + .field("app_version", &self.appinfo.app_version) + .field("app_core_mode", &self.appinfo.app_core_mode) + .field("app_is_admin", &self.appinfo.app_is_admin) + .finish() + } +} + +impl Display for Platform { + #[inline] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}", + self.sysinfo.system_name, + self.sysinfo.system_version, + self.sysinfo.system_kernel_version, + self.sysinfo.system_arch, + self.appinfo.app_version, + self.appinfo.app_core_mode, + self.appinfo.app_is_admin + ) + } +} + +impl Platform { + #[inline] + fn new() -> Self { + Self::default() + } +} + +#[inline] +fn is_binary_admin() -> bool { + #[cfg(not(windows))] + unsafe { + libc::geteuid() == 0 + } + #[cfg(windows)] + Token::with_current_process() + .and_then(|token| token.privilege_level()) + .map(|level| level != PrivilegeLevel::NotPrivileged) + .unwrap_or(false) +} + +#[inline] +pub fn list_network_interfaces() -> Vec { + let mut networks = Networks::new(); + networks.refresh(false); + networks.keys().map(|name| name.to_owned()).collect() +} + +#[inline] +pub fn set_app_core_mode(app: &tauri::AppHandle, mode: impl Into) { + let platform_spec = app.state::>(); + let mut spec = platform_spec.write(); + spec.appinfo.app_core_mode = mode.into(); +} + +#[inline] +pub fn init() -> TauriPlugin { + Builder::new("clash_verge_sysinfo") + // TODO 从 clash-verge 中迁移获取系统信息的 commnand 并实现优雅 structure.field 访问 + // .invoke_handler(tauri::generate_handler![greet]) + .setup(move |app, _api| { + let app_version = app.package_info().version.to_string(); + let is_admin = is_binary_admin(); + + let mut platform_spec = Platform::new(); + platform_spec.appinfo.app_version = app_version; + platform_spec.appinfo.app_is_admin = is_admin; + + app.manage(RwLock::new(platform_spec)); + Ok(()) + }) + .build() +} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 80dde89ab..319d3b4bb 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,6 +33,7 @@ tauri-build = { version = "2.5.2", features = [] } clash-verge-draft = { workspace = true } clash-verge-logging = { workspace = true } clash-verge-signal = { workspace = true } +tauri-plugin-clash-verge-sysinfo = { workspace = true } tauri = { workspace = true, features = [ "protocol-asset", "devtools", @@ -51,7 +52,6 @@ open = "5.3.3" dunce = "1.0.5" nanoid = "0.4" chrono = "0.4.42" -sysinfo = { version = "0.37.2", features = ["network", "system"] } boa_engine = "0.21.0" serde_json = "1.0.145" serde_yaml_ng = "0.10.0" @@ -80,7 +80,6 @@ base64 = "0.22.1" getrandom = "0.3.4" futures = "0.3.31" sys-locale = "0.3.2" -libc = "0.2.177" gethostname = "1.1.0" scopeguard = "1.2.0" tauri-plugin-notification = "2.3.3" @@ -100,8 +99,8 @@ arc-swap = "1.7.1" rust-i18n = "3.1.5" [target.'cfg(windows)'.dependencies] +deelevate = { workspace = true } runas = "=1.2.0" -deelevate = "0.2.0" winreg = "0.55.0" winapi = { version = "0.3.9", features = [ "winbase", diff --git a/src-tauri/src/cmd/network.rs b/src-tauri/src/cmd/network.rs index ab31de62b..9a18df7bf 100644 --- a/src-tauri/src/cmd/network.rs +++ b/src-tauri/src/cmd/network.rs @@ -4,6 +4,7 @@ use clash_verge_logging::{Type, logging}; use gethostname::gethostname; use network_interface::NetworkInterface; use serde_yaml_ng::Mapping; +use tauri_plugin_clash_verge_sysinfo; /// get the system proxy #[tauri::command] @@ -68,13 +69,7 @@ pub fn get_system_hostname() -> String { /// 获取网络接口列表 #[tauri::command] pub fn get_network_interfaces() -> Vec { - use sysinfo::Networks; - let mut result = Vec::new(); - let networks = Networks::new_with_refreshed_list(); - for (interface_name, _) in &networks { - result.push(interface_name.clone()); - } - result + tauri_plugin_clash_verge_sysinfo::list_network_interfaces() } /// 获取网络接口详细信息 diff --git a/src-tauri/src/cmd/system.rs b/src-tauri/src/cmd/system.rs index 01bd55a00..1248cea35 100644 --- a/src-tauri/src/cmd/system.rs +++ b/src-tauri/src/cmd/system.rs @@ -1,33 +1,17 @@ use std::sync::Arc; use super::CmdResult; -use crate::{ - core::{CoreManager, handle, manager::RunningMode}, - module::sysinfo::PlatformSpecification, -}; +use crate::core::{CoreManager, handle, manager::RunningMode}; use clash_verge_logging::{Type, logging}; -#[cfg(target_os = "windows")] -use deelevate::{PrivilegeLevel, Token}; -use once_cell::sync::Lazy; +use parking_lot::RwLock; +use tauri::Manager; +use tauri_plugin_clash_verge_sysinfo::Platform; use tauri_plugin_clipboard_manager::ClipboardExt as _; -use tokio::time::Instant; - -// 存储应用启动时间的全局变量 -static APP_START_TIME: Lazy = Lazy::new(Instant::now); -#[cfg(not(target_os = "windows"))] -static APPS_RUN_AS_ADMIN: Lazy = Lazy::new(|| unsafe { libc::geteuid() } == 0); -#[cfg(target_os = "windows")] -static APPS_RUN_AS_ADMIN: Lazy = Lazy::new(|| { - Token::with_current_process() - .and_then(|token| token.privilege_level()) - .map(|level| level != PrivilegeLevel::NotPrivileged) - .unwrap_or(false) -}); #[tauri::command] pub async fn export_diagnostic_info() -> CmdResult<()> { - let sysinfo = PlatformSpecification::new_sync(); - let info = format!("{sysinfo:?}"); + let app_handle = handle::Handle::app_handle(); + let info = app_handle.state::>().read().to_string(); let app_handle = handle::Handle::app_handle(); let cliboard = app_handle.clipboard(); @@ -37,10 +21,11 @@ pub async fn export_diagnostic_info() -> CmdResult<()> { Ok(()) } +// TODO 迁移,让新的结构体允许通过 tauri command 正确使用 structure.field 方式获取信息 #[tauri::command] pub async fn get_system_info() -> CmdResult { - let sysinfo = PlatformSpecification::new_sync(); - let info = format!("{sysinfo:?}"); + let app_handle = handle::Handle::app_handle(); + let info = app_handle.state::>().read().to_string(); Ok(info) } @@ -53,11 +38,22 @@ pub async fn get_running_mode() -> Result, String> { /// 获取应用的运行时间(毫秒) #[tauri::command] pub fn get_app_uptime() -> u128 { - APP_START_TIME.elapsed().as_millis() + let app_handle = handle::Handle::app_handle(); + let startup_time = app_handle + .state::>() + .read() + .appinfo + .app_startup_time; + startup_time.elapsed().as_millis() } /// 检查应用是否以管理员身份运行 #[tauri::command] pub fn is_admin() -> bool { - *APPS_RUN_AS_ADMIN + let app_handle = handle::Handle::app_handle(); + app_handle + .state::>() + .read() + .appinfo + .app_is_admin } diff --git a/src-tauri/src/core/manager/lifecycle.rs b/src-tauri/src/core/manager/lifecycle.rs index bd75a76dc..89926415c 100644 --- a/src-tauri/src/core/manager/lifecycle.rs +++ b/src-tauri/src/core/manager/lifecycle.rs @@ -1,17 +1,23 @@ use super::{CoreManager, RunningMode}; use crate::cmd::StringifyErr as _; use crate::config::{Config, IVerge}; +use crate::core::handle::Handle; use crate::core::{ logger::CLASH_LOGGER, service::{SERVICE_MANAGER, ServiceStatus}, }; use anyhow::Result; use clash_verge_logging::{Type, logging}; +use scopeguard::defer; use smartstring::alias::String; +use tauri_plugin_clash_verge_sysinfo; impl CoreManager { pub async fn start_core(&self) -> Result<()> { self.prepare_startup().await?; + defer! { + self.after_core_process(); + } match *self.get_running_mode() { RunningMode::Service => self.start_core_by_service().await, @@ -21,6 +27,9 @@ impl CoreManager { pub async fn stop_core(&self) -> Result<()> { CLASH_LOGGER.clear_logs().await; + defer! { + self.after_core_process(); + } match *self.get_running_mode() { RunningMode::Service => self.stop_core_by_service().await, @@ -74,6 +83,14 @@ impl CoreManager { Ok(()) } + fn after_core_process(&self) { + let app_handle = Handle::app_handle(); + tauri_plugin_clash_verge_sysinfo::set_app_core_mode( + app_handle, + self.get_running_mode().to_string(), + ); + } + #[cfg(target_os = "windows")] async fn wait_for_service_if_needed(&self) { use crate::{config::Config, constants::timing}; diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 15426919a..353d72282 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -254,9 +254,16 @@ async fn reinstall_service() -> Result<()> { #[cfg(target_os = "linux")] fn linux_running_as_root() -> bool { - const ROOT_UID: u32 = 0; - - unsafe { libc::geteuid() == ROOT_UID } + use crate::core::handle; + use parking_lot::RwLock; + use tauri::Manager as _; + use tauri_plugin_clash_verge_sysinfo::Platform; + let app_handle = handle::Handle::app_handle(); + app_handle + .state::>() + .read() + .appinfo + .app_is_admin } #[cfg(target_os = "macos")] diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 1e1beb746..d2de4dcd9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -57,6 +57,7 @@ mod app_init { .plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_deep_link::init()) .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_clash_verge_sysinfo::init()) .plugin( tauri_plugin_mihomo::Builder::new() .protocol(tauri_plugin_mihomo::models::Protocol::LocalSocket) diff --git a/src-tauri/src/module/mod.rs b/src-tauri/src/module/mod.rs index 80d2fa30d..97494e8f3 100644 --- a/src-tauri/src/module/mod.rs +++ b/src-tauri/src/module/mod.rs @@ -1,3 +1,2 @@ pub mod auto_backup; pub mod lightweight; -pub mod sysinfo; diff --git a/src-tauri/src/module/sysinfo.rs b/src-tauri/src/module/sysinfo.rs deleted file mode 100644 index 431eebaca..000000000 --- a/src-tauri/src/module/sysinfo.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::{ - cmd::system, - core::{CoreManager, handle}, -}; -use std::fmt::{self, Debug, Formatter}; -use sysinfo::System; - -pub struct PlatformSpecification { - system_name: String, - system_version: String, - system_kernel_version: String, - system_arch: String, - verge_version: String, - running_mode: String, - is_admin: bool, -} - -impl Debug for PlatformSpecification { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "System Name: {}\nSystem Version: {}\nSystem kernel Version: {}\nSystem Arch: {}\nVerge Version: {}\nRunning Mode: {}\nIs Admin: {}", - self.system_name, - self.system_version, - self.system_kernel_version, - self.system_arch, - self.verge_version, - self.running_mode, - self.is_admin - ) - } -} - -impl PlatformSpecification { - pub fn new() -> Self { - let system_name = System::name().unwrap_or_else(|| "Null".into()); - let system_version = System::long_os_version().unwrap_or_else(|| "Null".into()); - let system_kernel_version = System::kernel_version().unwrap_or_else(|| "Null".into()); - let system_arch = System::cpu_arch(); - - let handler = handle::Handle::app_handle(); - let verge_version = handler.package_info().version.to_string(); - - // 使用默认值避免在同步上下文中执行异步操作 - let running_mode = "NotRunning".to_string(); - - let is_admin = system::is_admin(); - - Self { - system_name, - system_version, - system_kernel_version, - system_arch, - verge_version, - running_mode, - is_admin, - } - } - - // 异步方法来获取完整的系统信息 - pub fn new_sync() -> Self { - let mut info = Self::new(); - - let running_mode = CoreManager::global().get_running_mode(); - info.running_mode = running_mode.to_string(); - - info - } -}