From 30b2b5b9c07def99b24253ec6c36184aa5584cb1 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Thu, 26 Mar 2026 12:56:30 +0100 Subject: [PATCH] feat(owlry): implement toggle behavior for repeated invocations Use a flock-based lock file at $XDG_RUNTIME_DIR/owlry/owlry-ui.lock to detect when another owlry UI instance is already running. If the lock is held, send a Toggle IPC command to the daemon and exit immediately instead of opening a second window. --- crates/owlry/src/main.rs | 54 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/crates/owlry/src/main.rs b/crates/owlry/src/main.rs index 83ac9b2..90a37aa 100644 --- a/crates/owlry/src/main.rs +++ b/crates/owlry/src/main.rs @@ -10,10 +10,43 @@ mod ui; use app::OwlryApp; use cli::{CliArgs, Command}; use log::{info, warn}; +use std::os::unix::io::AsRawFd; #[cfg(feature = "dev-logging")] use log::debug; +/// Try to acquire an exclusive lock on the UI lock file. +/// +/// Returns `Some(File)` if the lock was acquired (no other instance running), +/// or `None` if another instance already holds the lock. +/// The returned `File` must be kept alive for the duration of the process. +fn try_acquire_lock() -> Option { + use std::os::unix::fs::OpenOptionsExt; + + let lock_path = owlry_core::paths::socket_path() + .parent() + .unwrap() + .join("owlry-ui.lock"); + + // Ensure the parent directory exists + if let Some(parent) = lock_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .mode(0o600) + .open(&lock_path) + .ok() + .and_then(|f| { + let fd = f.as_raw_fd(); + let ret = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) }; + if ret == 0 { Some(f) } else { None } + }) +} + fn main() { let args = CliArgs::parse_args(); @@ -46,6 +79,27 @@ fn main() { debug!("CLI args: {:?}", args); } + // Toggle behavior: if another instance is already running, tell the daemon + // to toggle visibility and exit immediately. + let _lock_guard = match try_acquire_lock() { + Some(file) => file, + None => { + // Another instance holds the lock — send toggle to daemon and exit + info!("Another owlry instance detected, sending toggle"); + let socket_path = client::CoreClient::socket_path(); + if let Ok(mut client) = client::CoreClient::connect(&socket_path) { + if let Err(e) = client.toggle() { + eprintln!("Failed to toggle existing instance: {}", e); + std::process::exit(1); + } + } else { + eprintln!("Another instance is running but daemon is unreachable"); + std::process::exit(1); + } + std::process::exit(0); + } + }; + info!("Starting Owlry launcher"); // Diagnostic: log critical environment variables