240 lines
7.7 KiB
Rust
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");
|
|
}
|