641 lines
21 KiB
Rust
641 lines
21 KiB
Rust
use super::*;
|
|
use crate::native_i18n::{native_operation_failed, native_text, NativeText};
|
|
|
|
#[cfg(target_os = "macos")]
|
|
use objc2_app_kit::NSStatusItemBehavior;
|
|
#[cfg(target_os = "macos")]
|
|
use objc2_foundation::NSString;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
const MACOS_TRAY_ID: &str = "macos-tray";
|
|
|
|
#[cfg(target_os = "macos")]
|
|
#[derive(Default)]
|
|
pub(crate) struct MacosTrayClickState {
|
|
last_click: Option<Instant>,
|
|
sequence: u64,
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn set_macos_dock_visible(app_handle: &tauri::AppHandle, visible: bool) {
|
|
if let Err(error) = app_handle.set_dock_visibility(visible) {
|
|
eprintln!("Failed to update Dock icon visibility: {error}");
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn show_main_window_on_main_thread(app_handle: &tauri::AppHandle) {
|
|
let Some(window) = app_handle.get_webview_window("main") else {
|
|
return;
|
|
};
|
|
set_macos_dock_visible(app_handle, true);
|
|
if let Err(error) = window.show() {
|
|
eprintln!("Failed to show the main window: {error}");
|
|
set_macos_dock_visible(app_handle, false);
|
|
return;
|
|
}
|
|
if window.is_minimized().unwrap_or(false) {
|
|
if let Err(error) = window.unminimize() {
|
|
eprintln!("Failed to restore the main window: {error}");
|
|
}
|
|
}
|
|
if let Err(error) = window.set_focus() {
|
|
eprintln!("Failed to focus the main window: {error}");
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn show_main_window(app_handle: &tauri::AppHandle) {
|
|
if MainThreadMarker::new().is_some() {
|
|
show_main_window_on_main_thread(app_handle);
|
|
return;
|
|
}
|
|
|
|
let app_handle = app_handle.clone();
|
|
if let Err(error) = app_handle.clone().run_on_main_thread(move || {
|
|
show_main_window_on_main_thread(&app_handle);
|
|
}) {
|
|
eprintln!("Failed to schedule showing the main window: {error}");
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn show_macos_tray_menu<R: tauri::Runtime>(tray: &TrayIcon<R>) {
|
|
let result = tray.with_inner_tray_icon(|tray_icon| {
|
|
let Some(status_item) = tray_icon.ns_status_item() else {
|
|
return;
|
|
};
|
|
let mtm = MainThreadMarker::new().expect("tray menu must be shown on the main thread");
|
|
if let Some(menu) = status_item.menu(mtm) {
|
|
#[allow(deprecated)]
|
|
status_item.popUpStatusItemMenu(&menu);
|
|
}
|
|
});
|
|
|
|
if let Err(error) = result {
|
|
eprintln!("Failed to show the tray menu: {error}");
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn setup_macos_tray(app: &mut tauri::App<tauri::Wry>) -> tauri::Result<()> {
|
|
let locale = app
|
|
.state::<GuiConfigState>()
|
|
.snapshot()
|
|
.map(|config| config.locale)
|
|
.unwrap_or_else(|_| "en".to_string());
|
|
let open_main_window = MenuItem::with_id(
|
|
app,
|
|
"open-main-window",
|
|
native_text(&locale, NativeText::OpenMainWindow),
|
|
true,
|
|
None::<&str>,
|
|
)?;
|
|
let quit = MenuItem::with_id(
|
|
app,
|
|
"quit",
|
|
native_text(&locale, NativeText::Quit),
|
|
true,
|
|
None::<&str>,
|
|
)?;
|
|
let menu = Menu::with_items(app, &[&open_main_window, &quit])?;
|
|
let click_state = Arc::new(Mutex::new(MacosTrayClickState::default()));
|
|
let double_click_interval = Duration::from_secs_f64(NSEvent::doubleClickInterval());
|
|
|
|
let tray = TrayIconBuilder::with_id(MACOS_TRAY_ID)
|
|
.icon(
|
|
app.default_window_icon()
|
|
.cloned()
|
|
.expect("application icon is required for the tray"),
|
|
)
|
|
.menu(&menu)
|
|
.show_menu_on_left_click(false)
|
|
.on_menu_event(move |app_handle, event| match event.id().as_ref() {
|
|
"open-main-window" => show_main_window(app_handle),
|
|
"quit" => app_handle.exit(0),
|
|
_ => {}
|
|
})
|
|
.on_tray_icon_event(move |tray, event| {
|
|
if !matches!(
|
|
event,
|
|
TrayIconEvent::Click {
|
|
button: MouseButton::Left,
|
|
button_state: MouseButtonState::Up,
|
|
..
|
|
}
|
|
) {
|
|
return;
|
|
}
|
|
|
|
let now = Instant::now();
|
|
let Ok(mut state) = click_state.lock() else {
|
|
eprintln!("Failed to read tray click state");
|
|
return;
|
|
};
|
|
state.sequence += 1;
|
|
let sequence = state.sequence;
|
|
|
|
if state
|
|
.last_click
|
|
.is_some_and(|last_click| now.duration_since(last_click) <= double_click_interval)
|
|
{
|
|
state.last_click = None;
|
|
drop(state);
|
|
show_main_window(tray.app_handle());
|
|
return;
|
|
}
|
|
|
|
state.last_click = Some(now);
|
|
drop(state);
|
|
|
|
let app_handle = tray.app_handle().clone();
|
|
let click_state = Arc::clone(&click_state);
|
|
let tray_id = tray.id().clone();
|
|
thread::spawn(move || {
|
|
thread::sleep(double_click_interval);
|
|
let should_show_menu = match click_state.lock() {
|
|
Ok(mut state) if state.sequence == sequence => {
|
|
state.last_click = None;
|
|
true
|
|
}
|
|
Ok(_) => false,
|
|
Err(_) => {
|
|
eprintln!("Failed to read tray click state");
|
|
false
|
|
}
|
|
};
|
|
if !should_show_menu {
|
|
return;
|
|
}
|
|
|
|
if let Some(tray) = app_handle.tray_by_id(&tray_id) {
|
|
show_macos_tray_menu(&tray);
|
|
}
|
|
});
|
|
})
|
|
.build(app)?;
|
|
|
|
tray.with_inner_tray_icon(|tray_icon| {
|
|
if let Some(status_item) = tray_icon.ns_status_item() {
|
|
status_item.setBehavior(NSStatusItemBehavior::RemovalAllowed);
|
|
status_item.setAutosaveName(Some(&NSString::from_str(MACOS_TRAY_ID)));
|
|
}
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_ID: &str = "windows-tray";
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_OPEN_MENU_ID: &str = "windows-tray-open-main-window";
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_STATUS_MENU_ID: &str = "windows-tray-core-status";
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_TOGGLE_CORE_MENU_ID: &str = "windows-tray-toggle-core";
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_RESTART_CORE_MENU_ID: &str = "windows-tray-restart-core";
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
const WINDOWS_TRAY_QUIT_MENU_ID: &str = "windows-tray-quit";
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub(crate) fn linux_tray_available(app_handle: &tauri::AppHandle) -> bool {
|
|
use dbus::{arg::Variant, blocking::Connection};
|
|
|
|
if app_handle.tray_by_id(WINDOWS_TRAY_ID).is_none() {
|
|
return false;
|
|
}
|
|
|
|
let Ok(connection) = Connection::new_session() else {
|
|
return false;
|
|
};
|
|
let proxy = connection.with_proxy(
|
|
"org.kde.StatusNotifierWatcher",
|
|
"/StatusNotifierWatcher",
|
|
Duration::from_millis(500),
|
|
);
|
|
let host_registered: Result<(Variant<bool>,), _> = proxy.method_call(
|
|
"org.freedesktop.DBus.Properties",
|
|
"Get",
|
|
(
|
|
"org.kde.StatusNotifierWatcher",
|
|
"IsStatusNotifierHostRegistered",
|
|
),
|
|
);
|
|
matches!(host_registered, Ok((Variant(true),)))
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
#[derive(Clone, Copy)]
|
|
pub(crate) enum WindowsTrayCoreAction {
|
|
Toggle,
|
|
Restart,
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) struct WindowsTrayPresentation {
|
|
pub(crate) status_text: String,
|
|
pub(crate) toggle_text: String,
|
|
pub(crate) toggle_enabled: bool,
|
|
pub(crate) restart_enabled: bool,
|
|
pub(crate) tooltip: String,
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn windows_tray_presentation(
|
|
status: &CoreStatus,
|
|
busy: bool,
|
|
locale: &str,
|
|
) -> WindowsTrayPresentation {
|
|
let status_text = if busy {
|
|
native_text(locale, NativeText::CoreStatusWorking)
|
|
} else if !status.installed {
|
|
native_text(locale, NativeText::CoreStatusNotInstalled)
|
|
} else if status.running {
|
|
native_text(locale, NativeText::CoreStatusRunning)
|
|
} else {
|
|
native_text(locale, NativeText::CoreStatusStopped)
|
|
};
|
|
let toggle_text = if busy {
|
|
native_text(locale, NativeText::Working)
|
|
} else if status.running {
|
|
native_text(locale, NativeText::StopCore)
|
|
} else {
|
|
native_text(locale, NativeText::StartCore)
|
|
};
|
|
let tooltip = if busy {
|
|
native_text(locale, NativeText::TooltipCoreWorking)
|
|
} else if !status.installed {
|
|
native_text(locale, NativeText::TooltipCoreNotInstalled)
|
|
} else if status.running {
|
|
native_text(locale, NativeText::TooltipCoreRunning)
|
|
} else {
|
|
native_text(locale, NativeText::TooltipCoreStopped)
|
|
};
|
|
|
|
WindowsTrayPresentation {
|
|
status_text: status_text.to_string(),
|
|
toggle_text: toggle_text.to_string(),
|
|
toggle_enabled: status.installed && !busy,
|
|
restart_enabled: status.installed && status.running && !busy,
|
|
tooltip: tooltip.to_string(),
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) struct WindowsTrayState {
|
|
open_main_window: MenuItem<tauri::Wry>,
|
|
status_item: MenuItem<tauri::Wry>,
|
|
toggle_core_item: MenuItem<tauri::Wry>,
|
|
restart_core_item: MenuItem<tauri::Wry>,
|
|
quit_item: MenuItem<tauri::Wry>,
|
|
locale: Mutex<String>,
|
|
busy: AtomicBool,
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
impl WindowsTrayState {
|
|
fn locale(&self) -> String {
|
|
self.locale
|
|
.lock()
|
|
.map(|locale| locale.clone())
|
|
.unwrap_or_else(|_| "en".to_string())
|
|
}
|
|
|
|
fn set_locale(&self, locale: &str) {
|
|
let normalized = normalize_app_locale(locale);
|
|
if let Ok(mut current) = self.locale.lock() {
|
|
*current = normalized.to_string();
|
|
}
|
|
let labels = [
|
|
(
|
|
&self.open_main_window,
|
|
native_text(normalized, NativeText::OpenMainWindow),
|
|
),
|
|
(
|
|
&self.restart_core_item,
|
|
native_text(normalized, NativeText::RestartCore),
|
|
),
|
|
(&self.quit_item, native_text(normalized, NativeText::Quit)),
|
|
];
|
|
for (item, label) in labels {
|
|
if let Err(error) = item.set_text(label) {
|
|
eprintln!("Failed to update desktop tray language: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn begin_action(&self) -> bool {
|
|
if self
|
|
.busy
|
|
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
|
.is_err()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
let locale = self.locale();
|
|
if let Err(error) = self
|
|
.status_item
|
|
.set_text(native_text(&locale, NativeText::CoreStatusWorking)) {
|
|
eprintln!("Failed to update desktop tray core status: {error}");
|
|
}
|
|
if let Err(error) =
|
|
self.toggle_core_item
|
|
.set_text(native_text(&locale, NativeText::Working))
|
|
{
|
|
eprintln!("Failed to update desktop tray action text: {error}");
|
|
}
|
|
if let Err(error) = self.toggle_core_item.set_enabled(false) {
|
|
eprintln!("Failed to update desktop tray action state: {error}");
|
|
}
|
|
if let Err(error) = self.restart_core_item.set_enabled(false) {
|
|
eprintln!("Failed to update desktop tray restart state: {error}");
|
|
}
|
|
true
|
|
}
|
|
|
|
fn finish_action(&self) {
|
|
self.busy.store(false, Ordering::Release);
|
|
}
|
|
|
|
fn update(&self, status: &CoreStatus) -> String {
|
|
let presentation =
|
|
windows_tray_presentation(status, self.busy.load(Ordering::Acquire), &self.locale());
|
|
if let Err(error) = self.status_item.set_text(presentation.status_text.clone()) {
|
|
eprintln!("Failed to update desktop tray core status: {error}");
|
|
}
|
|
if let Err(error) = self
|
|
.toggle_core_item
|
|
.set_text(presentation.toggle_text.clone())
|
|
{
|
|
eprintln!("Failed to update desktop tray action text: {error}");
|
|
}
|
|
if let Err(error) = self
|
|
.toggle_core_item
|
|
.set_enabled(presentation.toggle_enabled)
|
|
{
|
|
eprintln!("Failed to update desktop tray action state: {error}");
|
|
}
|
|
if let Err(error) = self
|
|
.restart_core_item
|
|
.set_enabled(presentation.restart_enabled)
|
|
{
|
|
eprintln!("Failed to update desktop tray restart state: {error}");
|
|
}
|
|
presentation.tooltip
|
|
}
|
|
|
|
fn show_error(&self, error: &str) {
|
|
let mut summary = error.chars().take(48).collect::<String>();
|
|
if error.chars().count() > 48 {
|
|
summary.push('…');
|
|
}
|
|
let text = native_operation_failed(&self.locale(), &summary);
|
|
if let Err(update_error) = self.status_item.set_text(text) {
|
|
eprintln!("Failed to update desktop tray error state: {update_error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn show_windows_main_window(app_handle: &tauri::AppHandle) {
|
|
let Some(window) = app_handle.get_webview_window("main") else {
|
|
return;
|
|
};
|
|
if let Err(error) = window.show() {
|
|
eprintln!("Failed to show the main window: {error}");
|
|
return;
|
|
}
|
|
if window.is_minimized().unwrap_or(false) {
|
|
if let Err(error) = window.unminimize() {
|
|
eprintln!("Failed to restore the main window: {error}");
|
|
}
|
|
}
|
|
if let Err(error) = window.set_focus() {
|
|
eprintln!("Failed to focus the main window: {error}");
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn update_windows_tray_status(app_handle: &tauri::AppHandle, status: &CoreStatus) {
|
|
let tooltip = app_handle
|
|
.try_state::<WindowsTrayState>()
|
|
.map(|tray_state| tray_state.update(status))
|
|
.unwrap_or_else(|| windows_tray_presentation(status, false, "en").tooltip);
|
|
|
|
if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) {
|
|
if let Err(error) = tray.set_tooltip(Some(&tooltip)) {
|
|
eprintln!("Failed to update desktop tray tooltip: {error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn update_windows_tray_locale(
|
|
app_handle: &tauri::AppHandle,
|
|
locale: &str,
|
|
status: &CoreStatus,
|
|
) {
|
|
if let Some(tray_state) = app_handle.try_state::<WindowsTrayState>() {
|
|
tray_state.set_locale(locale);
|
|
}
|
|
update_windows_tray_status(app_handle, status);
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn show_windows_tray_action_error(app_handle: &tauri::AppHandle, error: &str) {
|
|
eprintln!("Desktop tray core operation failed: {error}");
|
|
let locale = app_handle
|
|
.try_state::<WindowsTrayState>()
|
|
.map(|state| state.locale())
|
|
.unwrap_or_else(|| "en".to_string());
|
|
if let Some(tray_state) = app_handle.try_state::<WindowsTrayState>() {
|
|
tray_state.show_error(error);
|
|
}
|
|
if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) {
|
|
if let Err(update_error) = tray.set_tooltip(Some(native_text(
|
|
&locale,
|
|
NativeText::TooltipCoreOperationFailed,
|
|
))) {
|
|
eprintln!("Failed to update desktop tray error tooltip: {update_error}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn run_windows_tray_core_action(
|
|
app_handle: &tauri::AppHandle,
|
|
action: WindowsTrayCoreAction,
|
|
) {
|
|
let Some(tray_state) = app_handle.try_state::<WindowsTrayState>() else {
|
|
return;
|
|
};
|
|
if !tray_state.begin_action() {
|
|
return;
|
|
}
|
|
if let Some(tray) = app_handle.tray_by_id(WINDOWS_TRAY_ID) {
|
|
if let Err(error) = tray.set_tooltip(Some(native_text(
|
|
&tray_state.locale(),
|
|
NativeText::TooltipCoreWorking,
|
|
))) {
|
|
eprintln!("Failed to update desktop tray busy tooltip: {error}");
|
|
}
|
|
}
|
|
|
|
let app_handle = app_handle.clone();
|
|
tauri::async_runtime::spawn_blocking(move || {
|
|
let process_state = app_handle.state::<CoreProcessState>();
|
|
let gui_config_state = app_handle.state::<GuiConfigState>();
|
|
let result = (|| -> Result<CoreStatus, String> {
|
|
let _guard = lock_core_operation(process_state.inner())?;
|
|
match action {
|
|
WindowsTrayCoreAction::Toggle => {
|
|
let config = gui_config_state.snapshot()?;
|
|
let status =
|
|
current_core_status(Some(process_state.inner()), Some(config.port))?;
|
|
if status.running {
|
|
stop_core_process_with_state(
|
|
process_state.inner(),
|
|
gui_config_state.inner(),
|
|
)
|
|
} else {
|
|
start_core_process_with_state(
|
|
process_state.inner(),
|
|
gui_config_state.inner(),
|
|
)
|
|
}
|
|
}
|
|
WindowsTrayCoreAction::Restart => {
|
|
restart_core_process_with_state(process_state.inner(), gui_config_state.inner())
|
|
}
|
|
}
|
|
})();
|
|
|
|
if let Some(tray_state) = app_handle.try_state::<WindowsTrayState>() {
|
|
tray_state.finish_action();
|
|
}
|
|
|
|
match result {
|
|
Ok(status) => emit_core_status(&app_handle, &status),
|
|
Err(error) => {
|
|
if let Ok(config) = gui_config_state.snapshot() {
|
|
if let Ok(status) =
|
|
current_core_status(Some(process_state.inner()), Some(config.port))
|
|
{
|
|
emit_core_status(&app_handle, &status);
|
|
}
|
|
}
|
|
show_windows_tray_action_error(&app_handle, &error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows"))]
|
|
pub(crate) fn setup_windows_tray(app: &mut tauri::App<tauri::Wry>) -> tauri::Result<()> {
|
|
let locale = app
|
|
.state::<GuiConfigState>()
|
|
.snapshot()
|
|
.map(|config| config.locale)
|
|
.unwrap_or_else(|_| "en".to_string());
|
|
let open_main_window = MenuItem::with_id(
|
|
app,
|
|
WINDOWS_TRAY_OPEN_MENU_ID,
|
|
native_text(&locale, NativeText::OpenMainWindow),
|
|
true,
|
|
None::<&str>,
|
|
)?;
|
|
let status_item = MenuItem::with_id(
|
|
app,
|
|
WINDOWS_TRAY_STATUS_MENU_ID,
|
|
native_text(&locale, NativeText::CoreStatusChecking),
|
|
false,
|
|
None::<&str>,
|
|
)?;
|
|
let toggle_core_item = MenuItem::with_id(
|
|
app,
|
|
WINDOWS_TRAY_TOGGLE_CORE_MENU_ID,
|
|
native_text(&locale, NativeText::StartCore),
|
|
false,
|
|
None::<&str>,
|
|
)?;
|
|
let restart_core_item = MenuItem::with_id(
|
|
app,
|
|
WINDOWS_TRAY_RESTART_CORE_MENU_ID,
|
|
native_text(&locale, NativeText::RestartCore),
|
|
false,
|
|
None::<&str>,
|
|
)?;
|
|
let quit = MenuItem::with_id(
|
|
app,
|
|
WINDOWS_TRAY_QUIT_MENU_ID,
|
|
native_text(&locale, NativeText::Quit),
|
|
true,
|
|
None::<&str>,
|
|
)?;
|
|
let separator_one = PredefinedMenuItem::separator(app)?;
|
|
let separator_two = PredefinedMenuItem::separator(app)?;
|
|
let menu = Menu::with_items(
|
|
app,
|
|
&[
|
|
&open_main_window,
|
|
&separator_one,
|
|
&status_item,
|
|
&toggle_core_item,
|
|
&restart_core_item,
|
|
&separator_two,
|
|
&quit,
|
|
],
|
|
)?;
|
|
|
|
// Linux AppIndicator implementations always open the menu when the icon is
|
|
// activated and do not emit tray click events. Keeping "Open Main Window"
|
|
// as the first item provides the reliable restore path on Linux; Windows
|
|
// additionally supports the double-click shortcut below.
|
|
TrayIconBuilder::with_id(WINDOWS_TRAY_ID)
|
|
.icon(
|
|
app.default_window_icon()
|
|
.cloned()
|
|
.expect("application icon is required for the tray"),
|
|
)
|
|
.tooltip("EasyCLIProxyAPI")
|
|
.menu(&menu)
|
|
.show_menu_on_left_click(false)
|
|
.on_menu_event(move |app_handle, event| match event.id().as_ref() {
|
|
WINDOWS_TRAY_OPEN_MENU_ID => show_windows_main_window(app_handle),
|
|
WINDOWS_TRAY_TOGGLE_CORE_MENU_ID => {
|
|
run_windows_tray_core_action(app_handle, WindowsTrayCoreAction::Toggle)
|
|
}
|
|
WINDOWS_TRAY_RESTART_CORE_MENU_ID => {
|
|
run_windows_tray_core_action(app_handle, WindowsTrayCoreAction::Restart)
|
|
}
|
|
WINDOWS_TRAY_QUIT_MENU_ID => app_handle.exit(0),
|
|
_ => {}
|
|
})
|
|
.on_tray_icon_event(move |tray, event| {
|
|
if matches!(
|
|
event,
|
|
TrayIconEvent::DoubleClick {
|
|
button: MouseButton::Left,
|
|
..
|
|
}
|
|
) {
|
|
show_windows_main_window(tray.app_handle());
|
|
}
|
|
})
|
|
.build(app)?;
|
|
|
|
let _ = app.manage(WindowsTrayState {
|
|
open_main_window,
|
|
status_item,
|
|
toggle_core_item,
|
|
restart_core_item,
|
|
quit_item: quit,
|
|
locale: Mutex::new(locale),
|
|
busy: AtomicBool::new(false),
|
|
});
|
|
|
|
Ok(())
|
|
}
|