diff --git a/UPDATELOG.md b/UPDATELOG.md index 973785cc..5472ddc2 100644 --- a/UPDATELOG.md +++ b/UPDATELOG.md @@ -16,6 +16,10 @@ - 优化重构订阅切换逻辑,可以随时中断载入过程,防止卡死 - 引入事件驱动代理管理器,优化代理配置更新逻辑,防止卡死 +### 🗑️ 移除内容 + +- 移除了 macOS tray 图标显示网络速率 + ## v2.3.1 ### 🐞 修复问题 diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index c1d7e2e8..bc2d54d1 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,5 +1,3 @@ -#[cfg(target_os = "macos")] -use crate::core::tray::Tray; use crate::{ config::*, core::{ @@ -980,9 +978,8 @@ impl CoreManager { } logging!(trace, Type::Core, "Initied core logic completed"); - #[cfg(target_os = "macos")] - logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await); - + // #[cfg(target_os = "macos")] + // logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await); Ok(()) } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 98c46004..0fb43344 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -12,15 +12,7 @@ use crate::{ }; use anyhow::Result; -#[cfg(target_os = "macos")] -use futures::StreamExt; use parking_lot::Mutex; -#[cfg(target_os = "macos")] -use parking_lot::RwLock; -#[cfg(target_os = "macos")] -pub use speed_rate::{SpeedRate, Traffic}; -#[cfg(target_os = "macos")] -use std::sync::Arc; use std::{ fs, sync::atomic::{AtomicBool, Ordering}, @@ -31,8 +23,6 @@ use tauri::{ tray::{MouseButton, MouseButtonState, TrayIconEvent}, AppHandle, Wry, }; -#[cfg(target_os = "macos")] -use tokio::sync::broadcast; use super::handle; @@ -64,10 +54,6 @@ fn should_handle_tray_click() -> bool { #[cfg(target_os = "macos")] pub struct Tray { - pub speed_rate: Arc>>, - shutdown_tx: Arc>>>, - is_subscribed: Arc>, - pub rate_cache: Arc>>, last_menu_update: Mutex>, menu_updating: AtomicBool, } @@ -187,10 +173,6 @@ impl Tray { #[cfg(target_os = "macos")] return TRAY.get_or_init(|| Tray { - speed_rate: Arc::new(Mutex::new(None)), - shutdown_tx: Arc::new(RwLock::new(None)), - is_subscribed: Arc::new(RwLock::new(false)), - rate_cache: Arc::new(Mutex::new(None)), last_menu_update: Mutex::new(None), menu_updating: AtomicBool::new(false), }); @@ -203,11 +185,6 @@ impl Tray { } pub fn init(&self) -> Result<()> { - #[cfg(target_os = "macos")] - { - let mut speed_rate = self.speed_rate.lock(); - *speed_rate = Some(SpeedRate::new()); - } Ok(()) } @@ -314,7 +291,7 @@ impl Tray { /// 更新托盘图标 #[cfg(target_os = "macos")] - pub fn update_icon(&self, rate: Option) -> Result<()> { + pub fn update_icon(&self, _rate: Option) -> Result<()> { let app_handle = match handle::Handle::global().app_handle() { Some(handle) => handle, None => { @@ -335,55 +312,18 @@ impl Tray { let system_mode = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); - let (is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) { + let (_is_custom_icon, icon_bytes) = match (*system_mode, *tun_mode) { (true, true) => TrayState::get_tun_tray_icon(), (true, false) => TrayState::get_sysproxy_tray_icon(), (false, true) => TrayState::get_tun_tray_icon(), (false, false) => TrayState::get_common_tray_icon(), }; - let enable_tray_speed = verge.enable_tray_speed.unwrap_or(false); - let enable_tray_icon = verge.enable_tray_icon.unwrap_or(true); let colorful = verge.tray_icon.clone().unwrap_or("monochrome".to_string()); let is_colorful = colorful == "colorful"; - if !enable_tray_speed { - let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?)); - let _ = tray.set_icon_as_template(!is_colorful); - return Ok(()); - } - - let rate = if let Some(rate) = rate { - Some(rate) - } else { - let guard = self.speed_rate.lock(); - if let Some(guard) = guard.as_ref() { - if let Some(rate) = guard.get_curent_rate() { - Some(rate) - } else { - Some(Rate::default()) - } - } else { - Some(Rate::default()) - } - }; - - let mut rate_guard = self.rate_cache.lock(); - if *rate_guard != rate { - *rate_guard = rate; - - let bytes = if enable_tray_icon { - Some(icon_bytes) - } else { - None - }; - - let rate = rate_guard.as_ref(); - if let Ok(rate_bytes) = SpeedRate::add_speed_text(is_custom_icon, bytes, rate) { - let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&rate_bytes)?)); - let _ = tray.set_icon_as_template(!is_custom_icon && !is_colorful); - } - } + let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?)); + let _ = tray.set_icon_as_template(!is_colorful); Ok(()) } @@ -498,155 +438,9 @@ impl Tray { Ok(()) } - /// 订阅流量数据 - #[cfg(target_os = "macos")] - pub async fn subscribe_traffic(&self) -> Result<()> { - log::info!(target: "app", "subscribe traffic"); - - // 如果已经订阅,先取消订阅 - if *self.is_subscribed.read() { - self.unsubscribe_traffic(); - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - - let (shutdown_tx, shutdown_rx) = broadcast::channel(3); - *self.shutdown_tx.write() = Some(shutdown_tx); - *self.is_subscribed.write() = true; - - let speed_rate = Arc::clone(&self.speed_rate); - let is_subscribed = Arc::clone(&self.is_subscribed); - - // 使用单线程防止阻塞主线程 - std::thread::Builder::new() - .name("traffic-monitor".into()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("Failed to build tokio runtime for traffic monitor"); - // 在单独的运行时中执行异步任务 - rt.block_on(async move { - let mut shutdown = shutdown_rx; - let speed_rate = speed_rate.clone(); - let is_subscribed = is_subscribed.clone(); - let mut consecutive_errors = 0; - let max_consecutive_errors = 5; - - let mut interval = tokio::time::interval(std::time::Duration::from_secs(10)); - - 'outer: loop { - if !*is_subscribed.read() { - log::info!(target: "app", "Traffic subscription has been cancelled"); - break; - } - - match tokio::time::timeout( - std::time::Duration::from_secs(5), - Traffic::get_traffic_stream() - ).await { - Ok(stream_result) => { - match stream_result { - Ok(mut stream) => { - consecutive_errors = 0; - - loop { - tokio::select! { - traffic_result = stream.next() => { - match traffic_result { - Some(Ok(traffic)) => { - if let Ok(Some(rate)) = tokio::time::timeout( - std::time::Duration::from_millis(50), - async { - let guard = speed_rate.try_lock(); - if let Some(guard) = guard { - if let Some(sr) = guard.as_ref() { - sr.update_and_check_changed(traffic.up, traffic.down) - } else { - None - } - } else { - None - } - } - ).await { - let _ = tokio::time::timeout( - std::time::Duration::from_millis(100), - async { let _ = Tray::global().update_icon(Some(rate)); } - ).await; - } - }, - Some(Err(e)) => { - log::error!(target: "app", "Traffic stream error: {}", e); - consecutive_errors += 1; - if consecutive_errors >= max_consecutive_errors { - log::error!(target: "app", "Too many errors, reconnecting traffic stream"); - break; - } - }, - None => { - log::info!(target: "app", "Traffic stream ended, reconnecting"); - break; - } - } - }, - _ = shutdown.recv() => { - log::info!(target: "app", "Received shutdown signal for traffic stream"); - break 'outer; - }, - _ = interval.tick() => { - if !*is_subscribed.read() { - log::info!(target: "app", "Traffic monitor detected subscription cancelled"); - break 'outer; - } - log::debug!(target: "app", "Traffic subscription periodic health check"); - }, - _ = tokio::time::sleep(std::time::Duration::from_secs(60)) => { - log::info!(target: "app", "Traffic stream max active time reached, reconnecting"); - break; - } - } - } - }, - Err(e) => { - log::error!(target: "app", "Failed to get traffic stream: {}", e); - consecutive_errors += 1; - if consecutive_errors >= max_consecutive_errors { - log::error!(target: "app", "Too many consecutive errors, pausing traffic monitoring"); - tokio::time::sleep(std::time::Duration::from_secs(30)).await; - consecutive_errors = 0; - } else { - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - } - }, - Err(_) => { - log::error!(target: "app", "Traffic stream initialization timed out"); - tokio::time::sleep(std::time::Duration::from_secs(2)).await; - } - } - - if !*is_subscribed.read() { - break; - } - } - log::info!(target: "app", "Traffic subscription thread terminated"); - }); - }) - .expect("Failed to spawn traffic monitor thread"); - - Ok(()) - } - /// 取消订阅 traffic 数据 #[cfg(target_os = "macos")] - pub fn unsubscribe_traffic(&self) { - log::info!(target: "app", "unsubscribe traffic"); - *self.is_subscribed.write() = false; - if let Some(tx) = self.shutdown_tx.write().take() { - drop(tx); - } - } + pub fn unsubscribe_traffic(&self) {} pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { log::info!(target: "app", "正在从AppHandle创建系统托盘"); diff --git a/src-tauri/src/core/tray/speed_rate.rs b/src-tauri/src/core/tray/speed_rate.rs index 06cbf610..8b137891 100644 --- a/src-tauri/src/core/tray/speed_rate.rs +++ b/src-tauri/src/core/tray/speed_rate.rs @@ -1,336 +1 @@ -use crate::{ - module::mihomo::{MihomoManager, Rate}, - utils::help::format_bytes_speed, -}; -use ab_glyph::FontArc; -use anyhow::Result; -use futures::Stream; -use image::{GenericImageView, Rgba, RgbaImage}; -use imageproc::drawing::draw_text_mut; -use parking_lot::Mutex; -use std::{io::Cursor, sync::Arc}; -use tokio_tungstenite::tungstenite::http; -use tungstenite::client::IntoClientRequest; -#[derive(Debug, Clone)] -pub struct SpeedRate { - rate: Arc>, - last_update: Arc>, -} -impl SpeedRate { - pub fn new() -> Self { - Self { - rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))), - last_update: Arc::new(Mutex::new(std::time::Instant::now())), - } - } - - pub fn update_and_check_changed(&self, up: u64, down: u64) -> Option { - let mut rates = self.rate.lock(); - let mut last_update = self.last_update.lock(); - let now = std::time::Instant::now(); - - // 限制更新频率为每秒最多2次(500ms) - if now.duration_since(*last_update).as_millis() < 500 { - return None; - } - - let (current, previous) = &mut *rates; - - // Avoid unnecessary float conversions for small value checks - let should_update = if current.up < 1000 && down < 1000 { - // For small values, always update to ensure accuracy - current.up != up || current.down != down - } else { - // For larger values, use integer math to check for >5% change - // Multiply by 20 instead of dividing by 0.05 to avoid floating point - let up_threshold = current.up / 20; - let down_threshold = current.down / 20; - - (up > current.up && up - current.up > up_threshold) - || (up < current.up && current.up - up > up_threshold) - || (down > current.down && down - current.down > down_threshold) - || (down < current.down && current.down - down > down_threshold) - }; - - if !should_update { - return None; - } - - *previous = current.clone(); - current.up = up; - current.down = down; - *last_update = now; - - if previous != current { - Some(current.clone()) - } else { - None - } - } - - pub fn get_curent_rate(&self) -> Option { - let rates = self.rate.lock(); - let (current, _) = &*rates; - Some(current.clone()) - } - - // 分离图标加载和速率渲染 - pub fn add_speed_text( - is_custom_icon: bool, - icon_bytes: Option>, - rate: Option<&Rate>, - ) -> Result> { - let rate = rate.unwrap_or(&Rate { up: 0, down: 0 }); - - let (mut icon_width, mut icon_height) = (0, 256); - let icon_image = if let Some(bytes) = icon_bytes.clone() { - let icon_image = image::load_from_memory(&bytes)?; - icon_width = icon_image.width(); - icon_height = icon_image.height(); - icon_image - } else { - // 返回一个空的 RGBA 图像 - image::DynamicImage::new_rgba8(0, 0) - }; - - let total_width = match (is_custom_icon, icon_bytes.is_some()) { - (true, true) => 510, - (true, false) => 740, - (false, false) => 740, - (false, true) => icon_width + 740, - }; - - // println!( - // "icon_height: {}, icon_wight: {}, total_width: {}", - // icon_height, icon_width, total_width - // ); - - // 创建新的透明画布 - let mut combined_image = RgbaImage::new(total_width, icon_height); - - // 将原始图标绘制到新画布的左侧 - if icon_bytes.is_some() { - for y in 0..icon_height { - for x in 0..icon_width { - let pixel = icon_image.get_pixel(x, y); - combined_image.put_pixel(x, y, pixel); - } - } - } - - let is_colorful = if let Some(bytes) = icon_bytes.clone() { - !crate::utils::help::is_monochrome_image_from_bytes(&bytes).unwrap_or(false) - } else { - false - }; - - // 选择文本颜色 - let (text_color, shadow_color) = if is_colorful { - ( - Rgba([144u8, 144u8, 144u8, 255u8]), - // Rgba([255u8, 255u8, 255u8, 128u8]), - Rgba([0u8, 0u8, 0u8, 128u8]), - ) - // ( - // Rgba([160u8, 160u8, 160u8, 255u8]), - // // Rgba([255u8, 255u8, 255u8, 128u8]), - // Rgba([0u8, 0u8, 0u8, 255u8]), - // ) - } else { - ( - Rgba([255u8, 255u8, 255u8, 255u8]), - Rgba([0u8, 0u8, 0u8, 128u8]), - ) - }; - // 减小字体大小以适应文本区域 - let font_data = include_bytes!("../../../assets/fonts/SF-Pro.ttf"); - let font = FontArc::try_from_vec(font_data.to_vec()).unwrap(); - let font_size = icon_height as f32 * 0.6; // 稍微减小字体 - let scale = ab_glyph::PxScale::from(font_size); - - // 使用更简洁的速率格式 - let up_text = format!("↑ {}", format_bytes_speed(rate.up)); - let down_text = format!("↓ {}", format_bytes_speed(rate.down)); - - // For test rate display - // let down_text = format!("↓ {}", format_bytes_speed(102 * 1020 * 1024)); - - // 计算文本位置,确保垂直间距合适 - // 修改文本位置为居右显示 - // 计算右对齐的文本位置 - // let up_text_width = imageproc::drawing::text_size(scale, &font, &up_text).0 as u32; - // let down_text_width = imageproc::drawing::text_size(scale, &font, &down_text).0 as u32; - // let up_text_x = total_width - up_text_width; - // let down_text_x = total_width - down_text_width; - - // 计算左对齐的文本位置 - let (up_text_x, down_text_x) = { - if is_custom_icon || icon_bytes.is_some() { - let text_left_offset = 30; - let left_begin = icon_width + text_left_offset; - (left_begin, left_begin) - } else { - (icon_width, icon_width) - } - }; - - // 优化垂直位置,使速率显示的高度和上下间距正好等于图标大小 - let text_height = font_size as i32; - let total_text_height = text_height * 2; - let up_y = (icon_height as i32 - total_text_height) / 2; - let down_y = up_y + text_height; - - // 绘制速率文本(先阴影后文字) - let shadow_offset = 1; - - // 绘制上行速率 - draw_text_mut( - &mut combined_image, - shadow_color, - up_text_x as i32 + shadow_offset, - up_y + shadow_offset, - scale, - &font, - &up_text, - ); - draw_text_mut( - &mut combined_image, - text_color, - up_text_x as i32, - up_y, - scale, - &font, - &up_text, - ); - - // 绘制下行速率 - draw_text_mut( - &mut combined_image, - shadow_color, - down_text_x as i32 + shadow_offset, - down_y + shadow_offset, - scale, - &font, - &down_text, - ); - draw_text_mut( - &mut combined_image, - text_color, - down_text_x as i32, - down_y, - scale, - &font, - &down_text, - ); - - // 将结果转换为 PNG 数据 - let mut bytes = Vec::new(); - combined_image.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)?; - Ok(bytes) - } -} - -#[derive(Debug, Clone)] -pub struct Traffic { - pub up: u64, - pub down: u64, -} - -impl Traffic { - pub async fn get_traffic_stream() -> Result>> - { - use futures::{ - future::FutureExt, - stream::{self, StreamExt}, - }; - use std::time::Duration; - - // 先处理错误和超时情况 - let stream = Box::pin( - stream::unfold((), move |_| async move { - 'retry: loop { - log::info!(target: "app", "establishing traffic websocket connection"); - let (url, token) = MihomoManager::get_traffic_ws_url(); - let mut request = match url.into_client_request() { - Ok(req) => req, - Err(e) => { - log::error!(target: "app", "failed to create websocket request: {}", e); - tokio::time::sleep(Duration::from_secs(2)).await; - continue 'retry; - } - }; - - request.headers_mut().insert(http::header::AUTHORIZATION, token); - - match tokio::time::timeout(Duration::from_secs(3), - tokio_tungstenite::connect_async(request) - ).await { - Ok(Ok((ws_stream, _))) => { - log::info!(target: "app", "traffic websocket connection established"); - // 设置流超时控制 - let traffic_stream = ws_stream - .take_while(|msg| { - let continue_stream = msg.is_ok(); - async move { continue_stream }.boxed() - }) - .filter_map(|msg| async move { - match msg { - Ok(msg) => { - if !msg.is_text() { - return None; - } - - match tokio::time::timeout( - Duration::from_millis(200), - async { msg.into_text() } - ).await { - Ok(Ok(text)) => { - match serde_json::from_str::(&text) { - Ok(json) => { - let up = json["up"].as_u64().unwrap_or(0); - let down = json["down"].as_u64().unwrap_or(0); - Some(Ok(Traffic { up, down })) - }, - Err(e) => { - log::warn!(target: "app", "traffic json parse error: {} for {}", e, text); - None - } - } - }, - Ok(Err(e)) => { - log::warn!(target: "app", "traffic text conversion error: {}", e); - None - }, - Err(_) => { - log::warn!(target: "app", "traffic text processing timeout"); - None - } - } - }, - Err(e) => { - log::error!(target: "app", "traffic websocket error: {}", e); - None - } - } - }); - - return Some((traffic_stream, ())); - }, - Ok(Err(e)) => { - log::error!(target: "app", "traffic websocket connection failed: {}", e); - }, - Err(_) => { - log::error!(target: "app", "traffic websocket connection timed out"); - } - } - - tokio::time::sleep(Duration::from_secs(2)).await; - } - }) - .flatten(), - ); - - Ok(stream) - } -} diff --git a/src-tauri/src/module/mihomo.rs b/src-tauri/src/module/mihomo.rs index 0fc40d31..37eb57a7 100644 --- a/src-tauri/src/module/mihomo.rs +++ b/src-tauri/src/module/mihomo.rs @@ -4,8 +4,6 @@ use once_cell::sync::Lazy; use parking_lot::{Mutex, RwLock}; use std::time::{Duration, Instant}; use tauri::http::HeaderMap; -#[cfg(target_os = "macos")] -use tauri::http::HeaderValue; // 缓存的最大有效期(5秒) const CACHE_TTL: Duration = Duration::from_secs(5); @@ -106,31 +104,5 @@ impl MihomoManager { Some((server, headers)) } - // 提供默认值的版本,避免在connection_info为None时panic - #[cfg(target_os = "macos")] - fn get_clash_client_info_or_default() -> (String, HeaderMap) { - Self::get_clash_client_info().unwrap_or_else(|| { - let mut headers = HeaderMap::new(); - headers.insert("Content-Type", "application/json".parse().unwrap()); - ("http://127.0.0.1:9090".to_string(), headers) - }) - } - - #[cfg(target_os = "macos")] - pub fn get_traffic_ws_url() -> (String, HeaderValue) { - let (url, headers) = MihomoManager::get_clash_client_info_or_default(); - let ws_url = url.replace("http://", "ws://") + "/traffic"; - let auth = headers - .get("Authorization") - .map(|val| val.to_str().unwrap_or("").to_string()) - .unwrap_or_default(); - - // 创建默认的空HeaderValue而不是使用unwrap_or_default - let token = match HeaderValue::from_str(&auth) { - Ok(v) => v, - Err(_) => HeaderValue::from_static(""), - }; - - (ws_url, token) - } + // 已移除未使用的 get_clash_client_info_or_default 和 get_traffic_ws_url 方法 } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 0df0573b..4c30b0f1 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -125,19 +125,6 @@ pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> { Ok(()) } -#[cfg(target_os = "macos")] -pub fn is_monochrome_image_from_bytes(data: &[u8]) -> anyhow::Result { - let img = image::load_from_memory(data)?; - let rgb_img = img.to_rgb8(); - - for pixel in rgb_img.pixels() { - if pixel[0] != pixel[1] || pixel[1] != pixel[2] { - return Ok(false); - } - } - Ok(true) -} - #[cfg(target_os = "linux")] pub fn linux_elevator() -> String { use std::process::Command; @@ -176,39 +163,3 @@ macro_rules! t { } }; } - -/// 将字节数转换为可读的流量字符串 -/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换 -/// -/// # Examples -/// ```not_run -/// format_bytes_speed(1000) // returns "1000B/s" -/// format_bytes_speed(1024) // returns "1.0KB/s" -/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s" -/// ``` -/// ``` -#[cfg(target_os = "macos")] -pub fn format_bytes_speed(speed: u64) -> String { - const UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; - let mut size = speed as f64; - let mut unit_index = 0; - - while size >= 1000.0 && unit_index < UNITS.len() - 1 { - size /= 1024.0; - unit_index += 1; - } - - format!("{:.1}{}/s", size, UNITS[unit_index]) -} - -#[cfg(target_os = "macos")] -#[test] -fn test_format_bytes_speed() { - assert_eq!(format_bytes_speed(0), "0.0B/s"); - assert_eq!(format_bytes_speed(1023), "1.0KB/s"); - assert_eq!(format_bytes_speed(1024), "1.0KB/s"); - assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s"); - assert_eq!(format_bytes_speed(1024 * 1024 * 1024), "1.0GB/s"); - assert_eq!(format_bytes_speed(1024 * 500), "500.0KB/s"); - assert_eq!(format_bytes_speed(1024 * 1024 * 2), "2.0MB/s"); -} diff --git a/src/components/setting/mods/layout-viewer.tsx b/src/components/setting/mods/layout-viewer.tsx index 55425e45..2b498b76 100644 --- a/src/components/setting/mods/layout-viewer.tsx +++ b/src/components/setting/mods/layout-viewer.tsx @@ -209,7 +209,7 @@ export const LayoutViewer = forwardRef((props, ref) => { )} - {OS === "macos" && ( + {/* {OS === "macos" && ( ((props, ref) => { - )} + )} */} {OS === "macos" && (