#![cfg_attr( all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows" )] mod commands; mod state; use state::AppState; use std::sync::Arc; use tauri::{ CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, WindowEvent, }; use tokio::sync::RwLock; #[cfg(windows)] mod single_instance { use windows::core::PCWSTR; use windows::Win32::Foundation::{CloseHandle, HANDLE, HWND, LPARAM, GetLastError, ERROR_ALREADY_EXISTS}; use windows::Win32::System::Threading::CreateMutexW; use windows::Win32::UI::WindowsAndMessaging::{ EnumWindows, GetWindowTextW, IsWindowVisible, SetForegroundWindow, ShowWindow, SW_RESTORE, }; static MUTEX_NAME: &str = "Global\\EasyRemoteClientMutex"; static WINDOW_TITLE: &str = "EasyRemote"; pub struct SingleInstance { _handle: HANDLE, } impl SingleInstance { pub fn new() -> Option { unsafe { let mutex_name: Vec = MUTEX_NAME.encode_utf16().chain(std::iter::once(0)).collect(); let handle = CreateMutexW(None, true, PCWSTR(mutex_name.as_ptr())).ok()?; if GetLastError() == ERROR_ALREADY_EXISTS { // 已有实例在运行,尝试激活它 activate_existing_window(); let _ = CloseHandle(handle); return None; } Some(Self { _handle: handle }) } } } fn activate_existing_window() { unsafe { let _ = EnumWindows(Some(enum_window_callback), LPARAM(0)); } } unsafe extern "system" fn enum_window_callback(hwnd: HWND, _: LPARAM) -> windows::Win32::Foundation::BOOL { if !IsWindowVisible(hwnd).as_bool() { return true.into(); } let mut title = [0u16; 256]; let len = GetWindowTextW(hwnd, &mut title); if len > 0 { let title_str = String::from_utf16_lossy(&title[..len as usize]); if title_str.contains(WINDOW_TITLE) { // 找到窗口,恢复并聚焦 let _ = ShowWindow(hwnd, SW_RESTORE); let _ = SetForegroundWindow(hwnd); return false.into(); // 停止枚举 } } true.into() } } #[cfg(not(windows))] mod single_instance { use std::fs::File; use std::path::PathBuf; pub struct SingleInstance { _lock_file: File, } impl SingleInstance { pub fn new() -> Option { let lock_path = get_lock_path(); // 尝试以独占方式打开锁文件 match File::options() .write(true) .create(true) .truncate(true) .open(&lock_path) { Ok(file) => { // 尝试获取文件锁 #[cfg(unix)] { use std::os::unix::io::AsRawFd; let fd = file.as_raw_fd(); let result = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) }; if result != 0 { eprintln!("EasyRemote 已在运行中"); return None; } } Some(Self { _lock_file: file }) } Err(_) => { eprintln!("EasyRemote 已在运行中"); None } } } } fn get_lock_path() -> PathBuf { dirs::runtime_dir() .or_else(dirs::cache_dir) .unwrap_or_else(std::env::temp_dir) .join("easyremote.lock") } } fn main() { // 单例检查 - 确保只运行一个实例 let _instance = match single_instance::SingleInstance::new() { Some(instance) => instance, None => { // 已有实例在运行,退出 eprintln!("EasyRemote 客户端已在运行中,将激活现有窗口"); return; } }; // 初始化日志 tracing_subscriber::fmt::init(); // 创建应用状态 let state = Arc::new(RwLock::new(AppState::new())); // 创建系统托盘菜单 let tray_menu = SystemTrayMenu::new() .add_item(CustomMenuItem::new("show", "显示主窗口")) .add_native_item(SystemTrayMenuItem::Separator) .add_item(CustomMenuItem::new("quit", "退出")); let system_tray = SystemTray::new().with_menu(tray_menu); tauri::Builder::default() .system_tray(system_tray) .on_system_tray_event(|app, event| match event { SystemTrayEvent::LeftClick { .. } => { // 左键点击显示窗口 if let Some(window) = app.get_window("main") { let _ = window.show(); let _ = window.set_focus(); } } SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() { "show" => { if let Some(window) = app.get_window("main") { let _ = window.show(); let _ = window.set_focus(); } } "quit" => { std::process::exit(0); } _ => {} }, _ => {} }) .on_window_event(|event| { // 关闭窗口时最小化到托盘而不是退出 if let WindowEvent::CloseRequested { api, .. } = event.event() { event.window().hide().unwrap(); api.prevent_close(); } }) .setup(|app| { // 设置窗口标题以便单例检测 if let Some(window) = app.get_window("main") { let _ = window.set_title("EasyRemote"); // 启动强制下线检测任务 let window_clone = window.clone(); std::thread::spawn(move || { loop { std::thread::sleep(std::time::Duration::from_millis(500)); // 检查是否需要强制下线 if commands::check_force_offline() { tracing::info!("执行强制下线,关闭应用"); // 关闭窗口并退出 let _ = window_clone.close(); break; } } }); } Ok(()) }) .manage(state) .invoke_handler(tauri::generate_handler![ // 初始化 commands::initialize, // 设备信息 commands::get_device_info, commands::refresh_verification_code, // 账号 commands::login, commands::logout, commands::register, commands::get_current_user, // 设备管理 commands::get_devices, commands::remove_device, // 连接 commands::connect_to_device, commands::disconnect, commands::get_connection_state, // 历史记录 commands::get_history, // 配置 commands::get_config, commands::save_config, commands::reconnect_server, // 开机启动 commands::get_autostart, commands::set_autostart, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }