rustdesk/src/tray.rs

261 lines
9.0 KiB
Rust
Raw Normal View History

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;
2023-02-09 21:28:42 +08:00
pub fn start_tray() {
2024-08-07 01:08:36 +08:00
if crate::ui_interface::get_builtin_option(hbb_common::config::keys::OPTION_HIDE_TRAY) == "Y" {
2024-08-07 01:19:29 +08:00
#[cfg(target_os = "macos")]
{
2024-08-07 01:08:36 +08:00
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
}
2024-08-07 01:19:29 +08:00
}
#[cfg(not(target_os = "macos"))]
{
2024-08-07 01:08:36 +08:00
return;
}
}
2023-02-09 21:28:42 +08:00
allow_err!(make_tray());
}
2024-08-07 23:26:03 +08:00
fn make_tray() -> hbb_common::ResultType<()> {
2023-02-09 21:28:42 +08:00
// https://github.com/tauri-apps/tray-icon/blob/dev/examples/tao.rs
use hbb_common::anyhow::Context;
use tao::event_loop::{ControlFlow, EventLoopBuilder};
2023-02-23 20:01:50 +08:00
use tray_icon::{
menu::{Menu, MenuEvent, MenuItem},
2024-07-08 14:45:18 +08:00
TrayIcon, TrayIconBuilder, TrayIconEvent as TrayEvent,
2023-02-23 20:01:50 +08:00
};
let icon;
2023-06-25 00:45:33 +08:00
#[cfg(target_os = "macos")]
{
icon = include_bytes!("../res/mac-tray-dark-x2.png"); // use as template, so color is not important
}
2023-06-25 00:45:33 +08:00
#[cfg(not(target_os = "macos"))]
{
icon = include_bytes!("../res/tray-icon.ico");
}
2024-03-12 22:13:28 +08:00
2023-02-09 21:28:42 +08:00
let (icon_rgba, icon_width, icon_height) = {
2024-03-12 22:13:28 +08:00
let image = load_icon_from_asset()
.unwrap_or(image::load_from_memory(icon).context("Failed to open icon path")?)
2023-02-09 21:28:42 +08:00
.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)
2023-02-09 21:28:42 +08:00
.context("Failed to open icon")?;
2022-11-18 18:36:25 +08:00
let mut event_loop = EventLoopBuilder::new().build();
2023-02-09 21:28:42 +08:00
2023-02-23 20:01:50 +08:00
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!(
2023-02-09 21:28:42 +08:00
"{} {}",
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"),
)
}
};
2024-07-08 14:45:18 +08:00
let mut _tray_icon: Arc<Mutex<Option<TrayIcon>>> = Default::default();
2023-02-09 21:28:42 +08:00
2023-02-23 20:01:50 +08:00
let menu_channel = MenuEvent::receiver();
2023-02-09 21:28:42 +08:00
let tray_channel = TrayEvent::receiver();
#[cfg(windows)]
let (ipc_sender, ipc_receiver) = std::sync::mpsc::channel::<Data>();
2023-02-09 21:28:42 +08:00
2023-06-06 01:47:02 +08:00
let open_func = move || {
if cfg!(not(feature = "flutter")) {
crate::run_me::<&str>(vec![]).ok();
return;
2023-06-30 22:13:16 +08:00
}
2023-06-06 01:47:02 +08:00
#[cfg(target_os = "macos")]
crate::platform::macos::handle_application_should_open_untitled_file();
#[cfg(target_os = "windows")]
{
2024-03-28 11:52:43 +08:00
// 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();
2023-06-06 01:47:02 +08:00
}
#[cfg(target_os = "linux")]
2023-06-10 18:24:03 +08:00
if !std::process::Command::new("xdg-open")
2024-02-25 13:29:06 +08:00
.arg(&crate::get_uri_prefix())
2023-06-10 18:24:03 +08:00
.spawn()
.is_ok()
{
crate::run_me::<&str>(vec![]).ok();
}
2023-06-06 01:47:02 +08:00
};
#[cfg(windows)]
std::thread::spawn(move || {
start_query_session_count(ipc_sender.clone());
});
#[cfg(windows)]
let mut last_click = std::time::Instant::now();
#[cfg(target_os = "macos")]
{
use tao::platform::macos::EventLoopExtMacOS;
event_loop.set_activation_policy(tao::platform::macos::ActivationPolicy::Accessory);
}
2024-07-08 14:45:18 +08:00
event_loop.run(move |event, _, control_flow| {
2023-06-06 01:47:02 +08:00
*control_flow = ControlFlow::WaitUntil(
std::time::Instant::now() + std::time::Duration::from_millis(100),
);
2023-02-09 21:28:42 +08:00
2024-07-08 14:45:18 +08:00
if let tao::event::Event::NewEvents(tao::event::StartCause::Init) = event {
// We create the icon once the event loop is actually running
// to prevent issues like https://github.com/tauri-apps/tray-icon/issues/90
let tray = TrayIconBuilder::new()
.with_menu(Box::new(tray_menu.clone()))
.with_tooltip(tooltip(0))
.with_icon(icon.clone())
.with_icon_as_template(true) // mac only
.build();
match tray {
Ok(tray) => _tray_icon = Arc::new(Mutex::new(Some(tray))),
Err(err) => {
log::error!("Failed to create tray icon: {}", err);
}
};
// We have to request a redraw here to have the icon actually show up.
// Tao only exposes a redraw method on the Window so we use core-foundation directly.
#[cfg(target_os = "macos")]
unsafe {
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
2023-02-23 20:01:50 +08:00
if let Ok(event) = menu_channel.try_recv() {
if event.id == quit_i.id() {
2023-07-02 11:22:52 +08:00
/* failed in windows, seems no permission to check system process
if !crate::check_process("--server", false) {
*control_flow = ControlFlow::Exit;
return;
}
2023-07-02 11:22:52 +08:00
*/
if !crate::platform::uninstall_service(false, false) {
*control_flow = ControlFlow::Exit;
}
2023-06-06 01:47:02 +08:00
} else if event.id == open_i.id() {
open_func();
2023-02-23 20:01:50 +08:00
}
}
if let Ok(_event) = tray_channel.try_recv() {
2023-06-06 01:47:02 +08:00
#[cfg(target_os = "windows")]
match _event {
TrayEvent::Click {
button,
button_state,
..
} => {
if button == tray_icon::MouseButton::Left
&& button_state == tray_icon::MouseButtonState::Up
{
if last_click.elapsed() < std::time::Duration::from_secs(1) {
return;
}
open_func();
last_click = std::time::Instant::now();
}
}
_ => {}
2023-02-23 20:01:50 +08:00
}
2023-02-09 21:28:42 +08:00
}
#[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))));
}
_ => {}
}
}
2023-02-09 21:28:42 +08:00
});
2022-11-18 18:36:25 +08:00
}
#[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;
}
}
2024-03-12 22:13:28 +08:00
fn load_icon_from_asset() -> Option<image::DynamicImage> {
2024-03-24 20:09:37 +08:00
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")]
2024-03-24 22:17:40 +08:00
let path = path.join("../Frameworks/App.framework/Resources/flutter_assets/assets/icon.png");
2024-03-12 22:13:28 +08:00
#[cfg(windows)]
2024-03-24 20:09:37 +08:00
let path = path.join(r"data\flutter_assets\assets\icon.png");
2024-05-10 15:11:48 +08:00
#[cfg(target_os = "linux")]
let path = path.join(r"data/flutter_assets/assets/icon.png");
2024-03-24 20:09:37 +08:00
if path.exists() {
if let Ok(image) = image::open(path) {
return Some(image);
2024-03-12 22:13:28 +08:00
}
}
None
}