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.
This commit is contained in:
2026-03-26 12:56:30 +01:00
parent 5be21aadc6
commit 30b2b5b9c0

View File

@@ -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<std::fs::File> {
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