easyremote/crates/client-tauri/src/main.rs

240 lines
7.7 KiB
Rust

#![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<Self> {
unsafe {
let mutex_name: Vec<u16> = 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<Self> {
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");
}