mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
feat: macos system tray addition rate display
This commit is contained in:
@@ -73,7 +73,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult {
|
||||
match CoreManager::global().update_config().await {
|
||||
Ok(_) => {
|
||||
handle::Handle::refresh_clash();
|
||||
let _ = handle::Handle::update_systray_tooltip();
|
||||
let _ = tray::Tray::global().update_tooltip();
|
||||
Config::profiles().apply();
|
||||
wrap_err!(Config::profiles().data().save_file())?;
|
||||
Ok(())
|
||||
|
||||
@@ -156,17 +156,20 @@ impl IClashTemp {
|
||||
}
|
||||
|
||||
pub fn guard_mixed_port(config: &Mapping) -> u16 {
|
||||
let mut port = config
|
||||
.get("mixed-port")
|
||||
let raw_value = config.get("mixed-port");
|
||||
|
||||
let mut port = raw_value
|
||||
.and_then(|value| match value {
|
||||
Value::String(val_str) => val_str.parse().ok(),
|
||||
Value::Number(val_num) => val_num.as_u64().map(|u| u as u16),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(7897);
|
||||
|
||||
if port == 0 {
|
||||
port = 7897;
|
||||
}
|
||||
|
||||
port
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,12 @@ pub fn parse_check_output(log: String) -> String {
|
||||
log
|
||||
}
|
||||
|
||||
pub fn get_traffic_ws_url() -> Result<String> {
|
||||
let (url, _) = clash_client_info()?;
|
||||
let ws_url = url.replace("http://", "ws://") + "/traffic";
|
||||
Ok(ws_url)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_check_output() {
|
||||
let str1 = r#"xxxx\n time="2022-11-18T20:42:58+08:00" level=error msg="proxy 0: 'alpn' expected type 'string', got unconvertible type '[]interface {}'""#;
|
||||
|
||||
@@ -76,7 +76,6 @@ impl CoreManager {
|
||||
service::stop_core_by_service().await?;
|
||||
}
|
||||
*running = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -96,6 +95,7 @@ impl CoreManager {
|
||||
service::run_core_by_service(&config_path).await?;
|
||||
*running = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use super::tray::Tray;
|
||||
use crate::log_err;
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
@@ -65,27 +63,6 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
/// update the system tray state
|
||||
pub fn update_systray_part() -> Result<()> {
|
||||
Tray::update_part()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_systray_menu() -> Result<()> {
|
||||
Tray::update_menu()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_systray_icon() -> Result<()> {
|
||||
Tray::update_icon()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_systray_tooltip() -> Result<()> {
|
||||
Tray::update_tooltip()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_is_exiting(&self) {
|
||||
let mut is_exiting = self.is_exiting.write();
|
||||
*is_exiting = true;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
pub mod speed_rate;
|
||||
use speed_rate::Rate;
|
||||
pub use speed_rate::{SpeedRate, Traffic};
|
||||
|
||||
use crate::utils::dirs;
|
||||
use crate::{
|
||||
cmds,
|
||||
@@ -6,6 +10,10 @@ use crate::{
|
||||
utils::resolve::{self, VERSION},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use futures::StreamExt;
|
||||
use once_cell::sync::OnceCell;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::sync::Arc;
|
||||
use tauri::AppHandle;
|
||||
use tauri::{
|
||||
menu::CheckMenuItem,
|
||||
@@ -15,12 +23,33 @@ use tauri::{
|
||||
menu::{MenuEvent, MenuItem, PredefinedMenuItem, Submenu},
|
||||
Wry,
|
||||
};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
use super::handle;
|
||||
pub struct Tray {}
|
||||
pub struct Tray {
|
||||
pub speed_rate: Arc<Mutex<Option<SpeedRate>>>,
|
||||
#[cfg(target_os = "macos")]
|
||||
shutdown_tx: Arc<RwLock<Option<broadcast::Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl Tray {
|
||||
pub fn create_systray() -> Result<()> {
|
||||
pub fn global() -> &'static Tray {
|
||||
static TRAY: OnceCell<Tray> = OnceCell::new();
|
||||
|
||||
TRAY.get_or_init(|| Tray {
|
||||
speed_rate: Arc::new(Mutex::new(None)),
|
||||
#[cfg(target_os = "macos")]
|
||||
shutdown_tx: Arc::new(RwLock::new(None)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init(&self) -> Result<()> {
|
||||
let mut speed_rate = self.speed_rate.lock();
|
||||
*speed_rate = Some(SpeedRate::new());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_systray(&self) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let tray_incon_id = TrayIconId::new("main");
|
||||
let tray = app_handle.tray_by_id(&tray_incon_id).unwrap();
|
||||
@@ -64,7 +93,7 @@ impl Tray {
|
||||
}
|
||||
|
||||
/// 更新托盘菜单
|
||||
pub fn update_menu() -> Result<()> {
|
||||
pub fn update_menu(&self) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let verge = Config::verge().latest().clone();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
@@ -91,7 +120,7 @@ impl Tray {
|
||||
}
|
||||
|
||||
/// 更新托盘图标
|
||||
pub fn update_icon() -> Result<()> {
|
||||
pub fn update_icon(&self, rate: Option<Rate>) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let verge = Config::verge().latest().clone();
|
||||
let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false);
|
||||
@@ -109,12 +138,12 @@ impl Tray {
|
||||
let icon_bytes = if *system_proxy && !*tun_mode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-sys.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../../icons/tray-icon-sys.ico").to_vec(),
|
||||
_ => include_bytes!("../../../icons/tray-icon-sys-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let mut icon = include_bytes!("../../icons/tray-icon-sys.ico").to_vec();
|
||||
let mut icon = include_bytes!("../../../icons/tray-icon-sys.ico").to_vec();
|
||||
if *sysproxy_tray_icon {
|
||||
let icon_dir_path = dirs::app_home_dir()?.join("icons");
|
||||
let png_path = icon_dir_path.join("sysproxy.png");
|
||||
@@ -129,8 +158,8 @@ impl Tray {
|
||||
} else if *tun_mode {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => include_bytes!("../../icons/tray-icon-tun.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../../icons/tray-icon-tun.ico").to_vec(),
|
||||
_ => include_bytes!("../../../icons/tray-icon-tun-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -149,8 +178,8 @@ impl Tray {
|
||||
} else {
|
||||
#[cfg(target_os = "macos")]
|
||||
let mut icon = match tray_icon.as_str() {
|
||||
"colorful" => include_bytes!("../../icons/tray-icon.ico").to_vec(),
|
||||
_ => include_bytes!("../../icons/tray-icon-mono.ico").to_vec(),
|
||||
"colorful" => include_bytes!("../../../icons/tray-icon.ico").to_vec(),
|
||||
_ => include_bytes!("../../../icons/tray-icon-mono.ico").to_vec(),
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
@@ -172,6 +201,14 @@ impl Tray {
|
||||
{
|
||||
let is_template =
|
||||
crate::utils::help::is_monochrome_image_from_bytes(&icon_bytes).unwrap_or(false);
|
||||
// 如果rate为None,获取当前速率
|
||||
let rate = rate.or_else(|| {
|
||||
self.speed_rate
|
||||
.lock()
|
||||
.as_ref()
|
||||
.and_then(|speed_rate| speed_rate.get_curent_rate())
|
||||
});
|
||||
let icon_bytes = SpeedRate::add_speed_text(icon_bytes, rate)?;
|
||||
let _ = tray.set_icon(Some(tauri::image::Image::from_bytes(&icon_bytes)?));
|
||||
let _ = tray.set_icon_as_template(is_template);
|
||||
}
|
||||
@@ -183,7 +220,7 @@ impl Tray {
|
||||
}
|
||||
|
||||
/// 更新托盘提示
|
||||
pub fn update_tooltip() -> Result<()> {
|
||||
pub fn update_tooltip(&self) -> Result<()> {
|
||||
let app_handle = handle::Handle::global().app_handle().unwrap();
|
||||
let use_zh = { Config::verge().latest().language == Some("zh".into()) };
|
||||
let version = VERSION.get().unwrap();
|
||||
@@ -223,12 +260,55 @@ impl Tray {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_part() -> Result<()> {
|
||||
Self::update_menu()?;
|
||||
Self::update_icon()?;
|
||||
Self::update_tooltip()?;
|
||||
pub fn update_part(&self) -> Result<()> {
|
||||
self.update_menu()?;
|
||||
self.update_icon(None)?;
|
||||
self.update_tooltip()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 订阅流量数据
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn subscribe_traffic(&self) -> Result<()> {
|
||||
log::info!(target: "app", "subscribe traffic");
|
||||
let (shutdown_tx, shutdown_rx) = broadcast::channel(1);
|
||||
*self.shutdown_tx.write() = Some(shutdown_tx);
|
||||
|
||||
let speed_rate = Arc::clone(&self.speed_rate);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut shutdown = shutdown_rx;
|
||||
if let Ok(mut stream) = Traffic::get_traffic_stream().await {
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some(traffic) = stream.next() => {
|
||||
if let Ok(traffic) = traffic {
|
||||
let guard = speed_rate.lock();
|
||||
if let Some(sr) = guard.as_ref() {
|
||||
if let Some(rate) = sr.update_and_check_changed(traffic.up, traffic.down) {
|
||||
let _ = Tray::global().update_icon(Some(rate));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ = shutdown.recv() => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 取消订阅 traffic 数据
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(unused)]
|
||||
pub fn unsubscribe_traffic(&self) {
|
||||
log::info!(target: "app", "unsubscribe traffic");
|
||||
if let Some(tx) = self.shutdown_tx.write().take() {
|
||||
drop(tx); // 发送端被丢弃时会自动发送关闭信号
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tray_menu(
|
||||
189
src-tauri/src/core/tray/speed_rate.rs
Normal file
189
src-tauri/src/core/tray/speed_rate.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use crate::core::clash_api::get_traffic_ws_url;
|
||||
use crate::utils::help::format_bytes_speed;
|
||||
use anyhow::Result;
|
||||
use futures::Stream;
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use imageproc::drawing::draw_text_mut;
|
||||
use parking_lot::Mutex;
|
||||
use rusttype::{Font, Scale};
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct Rate {
|
||||
pub up: u64,
|
||||
pub down: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpeedRate {
|
||||
rate: Arc<Mutex<(Rate, Rate)>>,
|
||||
}
|
||||
|
||||
impl SpeedRate {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
rate: Arc::new(Mutex::new((Rate::default(), Rate::default()))),
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新流量数据并返回变化后的速率(如果有变化)
|
||||
pub fn update_and_check_changed(&self, up: u64, down: u64) -> Option<Rate> {
|
||||
let mut rates = self.rate.lock();
|
||||
let (current, previous) = &mut *rates;
|
||||
|
||||
*previous = current.clone();
|
||||
current.up = up;
|
||||
current.down = down;
|
||||
|
||||
if previous != current {
|
||||
Some(current.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前的速率
|
||||
pub fn get_curent_rate(&self) -> Option<Rate> {
|
||||
let rates = self.rate.lock();
|
||||
let (current, _) = &*rates;
|
||||
Some(current.clone())
|
||||
}
|
||||
|
||||
/// 在图标上添加速率显示
|
||||
pub fn add_speed_text(icon: Vec<u8>, rate: Option<Rate>) -> Result<Vec<u8>> {
|
||||
let rate = rate.unwrap_or(Rate { up: 0, down: 0 });
|
||||
let img = image::load_from_memory(&icon)?;
|
||||
let (width, height) = (img.width(), img.height());
|
||||
|
||||
let mut image = ImageBuffer::new((width as f32 * 4.0) as u32, height);
|
||||
image::imageops::replace(&mut image, &img, 0, 0);
|
||||
|
||||
let font =
|
||||
Font::try_from_bytes(include_bytes!("../../../assets/fonts/SFCompact.ttf")).unwrap();
|
||||
|
||||
// 修改颜色和阴影参数
|
||||
let text_color = Rgba([255u8, 255u8, 255u8, 255u8]); // 纯白色
|
||||
let shadow_color = Rgba([0u8, 0u8, 0u8, 180u8]); // 半透明黑色阴影
|
||||
let base_size = (height as f32 * 0.5) as f32;
|
||||
let scale = Scale::uniform(base_size);
|
||||
|
||||
let up_text = format_bytes_speed(rate.up);
|
||||
let down_text = format_bytes_speed(rate.down);
|
||||
|
||||
// 计算文本位置(保持不变)
|
||||
let up_width = font
|
||||
.layout(&up_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
||||
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
||||
.last()
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let down_width = font
|
||||
.layout(&down_text, scale, rusttype::Point { x: 0.0, y: 0.0 })
|
||||
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
|
||||
.last()
|
||||
.unwrap_or(0.0);
|
||||
|
||||
let right_margin = 8;
|
||||
let canvas_width = width * 4;
|
||||
let up_x = canvas_width as f32 - up_width - right_margin as f32;
|
||||
let down_x = canvas_width as f32 - down_width - right_margin as f32;
|
||||
|
||||
// 添加阴影效果
|
||||
let shadow_offset = 1; // 阴影偏移量
|
||||
|
||||
// 绘制上行速率(先画阴影,再画文字)
|
||||
draw_text_mut(
|
||||
&mut image,
|
||||
shadow_color,
|
||||
up_x as i32 + shadow_offset,
|
||||
1 + shadow_offset,
|
||||
scale,
|
||||
&font,
|
||||
&up_text,
|
||||
);
|
||||
draw_text_mut(
|
||||
&mut image,
|
||||
text_color,
|
||||
up_x as i32,
|
||||
1,
|
||||
scale,
|
||||
&font,
|
||||
&up_text,
|
||||
);
|
||||
|
||||
// 绘制下行速率(先画阴影,再画文字)
|
||||
draw_text_mut(
|
||||
&mut image,
|
||||
shadow_color,
|
||||
down_x as i32 + shadow_offset,
|
||||
height as i32 - (base_size as i32) - 1 + shadow_offset,
|
||||
scale,
|
||||
&font,
|
||||
&down_text,
|
||||
);
|
||||
draw_text_mut(
|
||||
&mut image,
|
||||
text_color,
|
||||
down_x as i32,
|
||||
height as i32 - (base_size as i32) - 1,
|
||||
scale,
|
||||
&font,
|
||||
&down_text,
|
||||
);
|
||||
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
let mut cursor = Cursor::new(&mut bytes);
|
||||
image.write_to(&mut cursor, 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<impl Stream<Item = Result<Traffic, anyhow::Error>>>
|
||||
{
|
||||
use futures::stream::{self, StreamExt};
|
||||
use std::time::Duration;
|
||||
|
||||
let stream = Box::pin(
|
||||
stream::unfold((), |_| async {
|
||||
loop {
|
||||
let ws_url = get_traffic_ws_url().unwrap();
|
||||
|
||||
match tokio_tungstenite::connect_async(&ws_url).await {
|
||||
Ok((ws_stream, _)) => {
|
||||
log::info!(target: "app", "traffic ws connection established");
|
||||
return Some((
|
||||
ws_stream.map(|msg| {
|
||||
msg.map_err(anyhow::Error::from).and_then(|msg: Message| {
|
||||
let data = msg.into_text()?;
|
||||
let json: serde_json::Value = serde_json::from_str(&data)?;
|
||||
Ok(Traffic {
|
||||
up: json["up"].as_u64().unwrap_or(0),
|
||||
down: json["down"].as_u64().unwrap_or(0),
|
||||
})
|
||||
})
|
||||
}),
|
||||
(),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!(target: "app", "traffic ws connection failed: {e}");
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten(),
|
||||
);
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
}
|
||||
@@ -76,9 +76,9 @@ pub fn change_clash_mode(mode: String) {
|
||||
|
||||
if Config::clash().data().save_config().is_ok() {
|
||||
handle::Handle::refresh_clash();
|
||||
log_err!(handle::Handle::update_systray_menu());
|
||||
log_err!(tray::Tray::global().update_menu());
|
||||
#[cfg(target_os = "macos")]
|
||||
log_err!(handle::Handle::update_systray_icon());
|
||||
log_err!(tray::Tray::global().update_icon(None));
|
||||
}
|
||||
}
|
||||
Err(err) => log::error!(target: "app", "{err}"),
|
||||
@@ -141,9 +141,9 @@ pub async fn patch_clash(patch: Mapping) -> Result<()> {
|
||||
CoreManager::global().restart_core().await?;
|
||||
} else {
|
||||
if patch.get("mode").is_some() {
|
||||
log_err!(handle::Handle::update_systray_menu());
|
||||
log_err!(tray::Tray::global().update_menu());
|
||||
#[cfg(target_os = "macos")]
|
||||
log_err!(handle::Handle::update_systray_icon());
|
||||
log_err!(tray::Tray::global().update_icon(None));
|
||||
}
|
||||
Config::runtime().latest().patch_config(patch);
|
||||
CoreManager::global().update_config().await?;
|
||||
@@ -267,7 +267,7 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> {
|
||||
}
|
||||
|
||||
if should_update_systray_icon {
|
||||
handle::Handle::update_systray_icon()?;
|
||||
tray::Tray::global().update_icon(None)?;
|
||||
}
|
||||
|
||||
<Result<()>>::Ok(())
|
||||
|
||||
@@ -201,22 +201,34 @@ macro_rules! t {
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_value() {
|
||||
let test_1 = "upload=111; download=2222; total=3333; expire=444";
|
||||
let test_2 = "attachment; filename=Clash.yaml";
|
||||
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload").unwrap(), 111);
|
||||
assert_eq!(parse_str::<usize>(test_1, "download").unwrap(), 2222);
|
||||
assert_eq!(parse_str::<usize>(test_1, "total").unwrap(), 3333);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire").unwrap(), 444);
|
||||
assert_eq!(
|
||||
parse_str::<String>(test_2, "filename").unwrap(),
|
||||
format!("Clash.yaml")
|
||||
);
|
||||
|
||||
assert_eq!(parse_str::<usize>(test_1, "aaa"), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "upload1"), None);
|
||||
assert_eq!(parse_str::<usize>(test_1, "expire1"), None);
|
||||
assert_eq!(parse_str::<usize>(test_2, "attachment"), None);
|
||||
/// 将字节数转换为可读的流量字符串
|
||||
/// 支持 B/s、KB/s、MB/s、GB/s 的自动转换
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// assert_eq!(format_bytes_speed(1000), "1000B/s");
|
||||
/// assert_eq!(format_bytes_speed(1024), "1.0KB/s");
|
||||
/// assert_eq!(format_bytes_speed(1024 * 1024), "1.0MB/s");
|
||||
/// ```
|
||||
pub fn format_bytes_speed(speed: u64) -> String {
|
||||
if speed < 1024 {
|
||||
format!("{}B/s", speed)
|
||||
} else if speed < 1024 * 1024 {
|
||||
format!("{:.1}KB/s", speed as f64 / 1024.0)
|
||||
} else if speed < 1024 * 1024 * 1024 {
|
||||
format!("{:.1}MB/s", speed as f64 / 1024.0 / 1024.0)
|
||||
} else {
|
||||
format!("{:.1}GB/s", speed as f64 / 1024.0 / 1024.0 / 1024.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_bytes_speed() {
|
||||
assert_eq!(format_bytes_speed(0), "0B/s");
|
||||
assert_eq!(format_bytes_speed(1023), "1023B/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");
|
||||
}
|
||||
|
||||
@@ -92,7 +92,8 @@ pub async fn resolve_setup(app: &mut App) {
|
||||
server::embed_server();
|
||||
|
||||
log::trace!(target: "app", "init system tray");
|
||||
log_err!(tray::Tray::create_systray());
|
||||
log_err!(tray::Tray::global().init());
|
||||
log_err!(tray::Tray::global().create_systray());
|
||||
|
||||
let silent_start = { Config::verge().data().enable_silent_start };
|
||||
if !silent_start.unwrap_or(false) {
|
||||
@@ -102,14 +103,21 @@ pub async fn resolve_setup(app: &mut App) {
|
||||
log_err!(sysopt::Sysopt::global().update_sysproxy().await);
|
||||
log_err!(sysopt::Sysopt::global().init_guard_sysproxy());
|
||||
|
||||
log_err!(handle::Handle::update_systray_part());
|
||||
log_err!(tray::Tray::global().update_part());
|
||||
log_err!(hotkey::Hotkey::global().init());
|
||||
log_err!(timer::Timer::global().init());
|
||||
|
||||
// 流量订阅
|
||||
#[cfg(target_os = "macos")]
|
||||
log_err!(tray::Tray::global().subscribe_traffic().await);
|
||||
}
|
||||
|
||||
/// reset system proxy
|
||||
pub fn resolve_reset() {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
#[cfg(target_os = "macos")]
|
||||
tray::Tray::global().unsubscribe_traffic();
|
||||
|
||||
log_err!(sysopt::Sysopt::global().reset_sysproxy().await);
|
||||
log_err!(CoreManager::global().stop_core().await);
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
Reference in New Issue
Block a user