Fix: Duplicate App Instances (#83)

This commit is contained in:
Youwes09
2026-05-16 15:41:07 -05:00
parent 6b56db7cf2
commit e3abc72f1b
3 changed files with 60 additions and 43 deletions
-1
View File
@@ -2008,7 +2008,6 @@ name = "moku"
version = "0.9.3" version = "0.9.3"
dependencies = [ dependencies = [
"dirs 5.0.1", "dirs 5.0.1",
"libc",
"reqwest 0.12.28", "reqwest 0.12.28",
"serde", "serde",
"serde_json", "serde_json",
-1
View File
@@ -28,7 +28,6 @@ serde_json = "1"
walkdir = "2" walkdir = "2"
sysinfo = "0.32" sysinfo = "0.32"
dirs = "5" dirs = "5"
libc = "0.2"
urlencoding = "2" urlencoding = "2"
tokio = { version = "1", features = ["rt-multi-thread"] } tokio = { version = "1", features = ["rt-multi-thread"] }
reqwest = { version = "0.12", features = ["blocking"] } reqwest = { version = "0.12", features = ["blocking"] }
+60 -41
View File
@@ -2,6 +2,8 @@ mod commands;
mod server; mod server;
use std::sync::Mutex; use std::sync::Mutex;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use tauri::{ use tauri::{
menu::{Menu, MenuItem, PredefinedMenuItem}, menu::{Menu, MenuItem, PredefinedMenuItem},
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
@@ -11,13 +13,70 @@ use tauri_plugin_shell::process::CommandChild;
pub struct ServerState(pub Mutex<Option<CommandChild>>); pub struct ServerState(pub Mutex<Option<CommandChild>>);
const IPC_PORT: u16 = 47823;
const HANDSHAKE: &[u8] = b"MOKU:1\n";
const FOCUS_CMD: &[u8] = b"focus\n";
fn do_quit(app: &tauri::AppHandle) { fn do_quit(app: &tauri::AppHandle) {
server::kill_tachidesk(app); server::kill_tachidesk(app);
app.exit(0); app.exit(0);
} }
fn start_instance_listener(app: tauri::AppHandle) {
std::thread::spawn(move || {
let Ok(listener) = TcpListener::bind(("127.0.0.1", IPC_PORT)) else {
return;
};
for stream in listener.incoming().flatten() {
handle_ipc_connection(stream, &app);
}
});
}
fn handle_ipc_connection(mut stream: TcpStream, app: &tauri::AppHandle) {
let mut buf = [0u8; 32];
let Ok(n) = stream.read(&mut buf) else { return };
let msg = &buf[..n];
if !msg.starts_with(HANDSHAKE) {
return;
}
let cmd = &msg[HANDSHAKE.len()..];
if cmd.starts_with(b"focus") {
let _ = stream.write_all(b"ok\n");
if let Some(win) = app.get_webview_window("main") {
let _ = win.show();
let _ = win.unminimize();
let _ = win.set_focus();
}
}
}
fn signal_existing_instance() -> bool {
let Ok(mut stream) = TcpStream::connect(("127.0.0.1", IPC_PORT)) else {
return false;
};
stream.set_read_timeout(Some(std::time::Duration::from_millis(500))).ok();
let mut msg = Vec::new();
msg.extend_from_slice(HANDSHAKE);
msg.extend_from_slice(FOCUS_CMD);
if stream.write_all(&msg).is_err() {
return false;
}
let mut resp = [0u8; 4];
matches!(stream.read(&mut resp), Ok(n) if resp[..n].starts_with(b"ok"))
}
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
if signal_existing_instance() {
std::process::exit(0);
}
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_store::Builder::new().build()) .plugin(tauri_plugin_store::Builder::new().build())
.plugin(tauri_plugin_discord_rpc::init()) .plugin(tauri_plugin_discord_rpc::init())
@@ -54,47 +113,7 @@ pub fn run() {
commands::biometric::windows_hello_available, commands::biometric::windows_hello_available,
]) ])
.setup(|app| { .setup(|app| {
let lock_path = app start_instance_listener(app.handle().clone());
.path()
.app_data_dir()
.unwrap_or_default()
.join(".moku.lock");
let lock_file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.open(&lock_path)
.ok();
let already_running = lock_file.as_ref().map(|f| {
#[cfg(unix)]
{
use std::os::unix::io::AsRawFd;
unsafe { libc::flock(f.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) != 0 }
}
#[cfg(windows)]
{
use std::os::windows::io::AsRawHandle;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
LockFileEx, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
};
let handle = HANDLE(f.as_raw_handle() as isize);
let mut overlapped = windows::Win32::System::IO::OVERLAPPED::default();
!unsafe {
LockFileEx(handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &mut overlapped)
}.as_bool()
}
#[cfg(not(any(unix, windows)))]
{ false }
}).unwrap_or(false);
if already_running {
app.handle().exit(0);
return Ok(());
}
std::mem::forget(lock_file);
let show = MenuItem::with_id(app, "show", "Show Moku", true, None::<&str>)?; let show = MenuItem::with_id(app, "show", "Show Moku", true, None::<&str>)?;
let sep = PredefinedMenuItem::separator(app)?; let sep = PredefinedMenuItem::separator(app)?;