220 lines
7.3 KiB
Rust
220 lines
7.3 KiB
Rust
use crate::client::translate;
|
|
#[cfg(windows)]
|
|
use crate::ipc::Data;
|
|
#[cfg(windows)]
|
|
use hbb_common::tokio;
|
|
use hbb_common::{allow_err, log};
|
|
use std::sync::{Arc, Mutex};
|
|
#[cfg(windows)]
|
|
use std::time::Duration;
|
|
|
|
pub fn start_tray() {
|
|
allow_err!(make_tray());
|
|
}
|
|
|
|
pub fn make_tray() -> hbb_common::ResultType<()> {
|
|
// https://github.com/tauri-apps/tray-icon/blob/dev/examples/tao.rs
|
|
use hbb_common::anyhow::Context;
|
|
use tao::event_loop::{ControlFlow, EventLoopBuilder};
|
|
use tray_icon::{
|
|
menu::{Menu, MenuEvent, MenuItem},
|
|
TrayIconBuilder, TrayIconEvent as TrayEvent,
|
|
};
|
|
let icon;
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
icon = include_bytes!("../res/mac-tray-dark-x2.png"); // use as template, so color is not important
|
|
}
|
|
#[cfg(not(target_os = "macos"))]
|
|
{
|
|
icon = include_bytes!("../res/tray-icon.ico");
|
|
}
|
|
|
|
let (icon_rgba, icon_width, icon_height) = {
|
|
let image = load_icon_from_asset()
|
|
.unwrap_or(image::load_from_memory(icon).context("Failed to open icon path")?)
|
|
.into_rgba8();
|
|
let (width, height) = image.dimensions();
|
|
let rgba = image.into_raw();
|
|
(rgba, width, height)
|
|
};
|
|
let icon = tray_icon::Icon::from_rgba(icon_rgba, icon_width, icon_height)
|
|
.context("Failed to open icon")?;
|
|
|
|
let event_loop = EventLoopBuilder::new().build();
|
|
|
|
let tray_menu = Menu::new();
|
|
let quit_i = MenuItem::new(translate("Exit".to_owned()), true, None);
|
|
let open_i = MenuItem::new(translate("Open".to_owned()), true, None);
|
|
tray_menu.append_items(&[&open_i, &quit_i]).ok();
|
|
let tooltip = |count: usize| {
|
|
if count == 0 {
|
|
format!(
|
|
"{} {}",
|
|
crate::get_app_name(),
|
|
translate("Service is running".to_owned()),
|
|
)
|
|
} else {
|
|
format!(
|
|
"{} - {}\n{}",
|
|
crate::get_app_name(),
|
|
translate("Ready".to_owned()),
|
|
translate("{".to_string() + &format!("{count}") + "} sessions"),
|
|
)
|
|
}
|
|
};
|
|
let _tray_icon = Some(
|
|
TrayIconBuilder::new()
|
|
.with_menu(Box::new(tray_menu))
|
|
.with_tooltip(tooltip(0))
|
|
.with_icon(icon)
|
|
.with_icon_as_template(true) // mac only
|
|
.build()?,
|
|
);
|
|
let _tray_icon = Arc::new(Mutex::new(_tray_icon));
|
|
|
|
let menu_channel = MenuEvent::receiver();
|
|
let tray_channel = TrayEvent::receiver();
|
|
#[cfg(windows)]
|
|
let (ipc_sender, ipc_receiver) = std::sync::mpsc::channel::<Data>();
|
|
let mut docker_hiden = false;
|
|
|
|
let open_func = move || {
|
|
if cfg!(not(feature = "flutter")) {
|
|
crate::run_me::<&str>(vec![]).ok();
|
|
return;
|
|
}
|
|
#[cfg(target_os = "macos")]
|
|
crate::platform::macos::handle_application_should_open_untitled_file();
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
// Do not use "start uni link" way, it may not work on some Windows, and pop out error
|
|
// dialog, I found on one user's desktop, but no idea why, Windows is shit.
|
|
// Use `run_me` instead.
|
|
// `allow_multiple_instances` in `flutter/windows/runner/main.cpp` allows only one instance without args.
|
|
crate::run_me::<&str>(vec![]).ok();
|
|
}
|
|
#[cfg(target_os = "linux")]
|
|
if !std::process::Command::new("xdg-open")
|
|
.arg(&crate::get_uri_prefix())
|
|
.spawn()
|
|
.is_ok()
|
|
{
|
|
crate::run_me::<&str>(vec![]).ok();
|
|
}
|
|
};
|
|
|
|
#[cfg(windows)]
|
|
std::thread::spawn(move || {
|
|
start_query_session_count(ipc_sender.clone());
|
|
});
|
|
#[cfg(windows)]
|
|
let mut last_click = std::time::Instant::now();
|
|
event_loop.run(move |_event, _, control_flow| {
|
|
if !docker_hiden {
|
|
#[cfg(target_os = "macos")]
|
|
crate::platform::macos::hide_dock();
|
|
docker_hiden = true;
|
|
}
|
|
*control_flow = ControlFlow::WaitUntil(
|
|
std::time::Instant::now() + std::time::Duration::from_millis(100),
|
|
);
|
|
|
|
if let Ok(event) = menu_channel.try_recv() {
|
|
if event.id == quit_i.id() {
|
|
/* failed in windows, seems no permission to check system process
|
|
if !crate::check_process("--server", false) {
|
|
*control_flow = ControlFlow::Exit;
|
|
return;
|
|
}
|
|
*/
|
|
if !crate::platform::uninstall_service(false, false) {
|
|
*control_flow = ControlFlow::Exit;
|
|
}
|
|
} else if event.id == open_i.id() {
|
|
open_func();
|
|
}
|
|
}
|
|
|
|
if let Ok(_event) = tray_channel.try_recv() {
|
|
#[cfg(target_os = "windows")]
|
|
if _event.click_type == tray_icon::ClickType::Left
|
|
|| _event.click_type == tray_icon::ClickType::Double
|
|
{
|
|
if last_click.elapsed() < std::time::Duration::from_secs(1) {
|
|
return;
|
|
}
|
|
open_func();
|
|
last_click = std::time::Instant::now();
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
if let Ok(data) = ipc_receiver.try_recv() {
|
|
match data {
|
|
Data::ControlledSessionCount(count) => {
|
|
_tray_icon
|
|
.lock()
|
|
.unwrap()
|
|
.as_mut()
|
|
.map(|t| t.set_tooltip(Some(tooltip(count))));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[tokio::main(flavor = "current_thread")]
|
|
async fn start_query_session_count(sender: std::sync::mpsc::Sender<Data>) {
|
|
let mut last_count = 0;
|
|
loop {
|
|
if let Ok(mut c) = crate::ipc::connect(1000, "").await {
|
|
let mut timer = crate::rustdesk_interval(tokio::time::interval(Duration::from_secs(1)));
|
|
loop {
|
|
tokio::select! {
|
|
res = c.next() => {
|
|
match res {
|
|
Err(err) => {
|
|
log::error!("ipc connection closed: {}", err);
|
|
break;
|
|
}
|
|
|
|
Ok(Some(Data::ControlledSessionCount(count))) => {
|
|
if count != last_count {
|
|
last_count = count;
|
|
sender.send(Data::ControlledSessionCount(count)).ok();
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
_ = timer.tick() => {
|
|
c.send(&Data::ControlledSessionCount(0)).await.ok();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
hbb_common::sleep(1.).await;
|
|
}
|
|
}
|
|
|
|
fn load_icon_from_asset() -> Option<image::DynamicImage> {
|
|
let Some(path) = std::env::current_exe().map_or(None, |x| x.parent().map(|x| x.to_path_buf()))
|
|
else {
|
|
return None;
|
|
};
|
|
#[cfg(target_os = "macos")]
|
|
let path = path.join("../Frameworks/App.framework/Resources/flutter_assets/assets/icon.png");
|
|
#[cfg(windows)]
|
|
let path = path.join(r"data\flutter_assets\assets\icon.png");
|
|
if path.exists() {
|
|
if let Ok(image) = image::open(path) {
|
|
return Some(image);
|
|
}
|
|
}
|
|
None
|
|
}
|