app_single_instance/lib.rs
1// src/lib.rs
2
3mod socket;
4mod listener;
5
6use std::io::Write;
7use interprocess::local_socket::prelude::*;
8
9/// A handle representing the primary (first) instance of the application.
10///
11/// Holds the IPC listener alive for the lifetime of the application.
12/// When dropped, cleans up the socket file on Unix platforms.
13pub struct PrimaryHandle {
14 #[cfg(unix)]
15 app_id: String,
16 rx: std::sync::mpsc::Receiver<()>,
17}
18
19impl PrimaryHandle {
20 /// Returns `true` if another instance has sent a wake-up signal since the last call.
21 ///
22 /// This is non-blocking and is intended to be polled from the application's main loop,
23 /// for example to bring the existing window to the foreground.
24 pub fn check_show(&self) -> bool {
25 self.rx.try_recv().is_ok()
26 }
27}
28
29impl Drop for PrimaryHandle {
30 fn drop(&mut self) {
31 #[cfg(unix)]
32 socket::cleanup(&self.app_id);
33 }
34}
35
36/// Checks whether another instance of the application is already running.
37///
38/// If a running instance is found, sends a wake-up signal to it and returns `true`.
39/// The caller should exit immediately in this case.
40///
41/// Returns `false` if no existing instance was detected, meaning the caller
42/// may proceed to launch as the primary instance.
43///
44/// # Arguments
45///
46/// * `app_id` - A unique identifier for the application, used to name the IPC socket.
47pub fn notify_if_running(app_id: &str) -> bool {
48 if let Ok(mut s) = LocalSocketStream::connect(socket::socket_name(app_id)) {
49 let _ = s.write_all(b"show\n");
50 return true;
51 }
52 false
53}
54
55/// Registers the current process as the primary instance and begins listening
56/// for signals from any subsequently launched instances.
57///
58/// Spawns a background thread that listens on a local socket. When a signal
59/// is received, `on_show` is called on the background thread and a message is
60/// also sent to [`PrimaryHandle::check_show`] for poll-based detection.
61///
62/// This function must be called only after [`notify_if_running`] has returned `false`.
63///
64/// # Arguments
65///
66/// * `app_id` - A unique identifier for the application, must match the one passed to [`notify_if_running`].
67/// * `on_show` - Callback invoked on the listener thread each time a wake-up signal is received.
68///
69/// # Returns
70///
71/// A [`PrimaryHandle`] that keeps the listener alive. Dropping it will shut down
72/// the listener and clean up resources.
73pub fn start_primary(app_id: &str, on_show: impl Fn() + Send + 'static) -> PrimaryHandle {
74 let rx = listener::start(app_id.to_string(), Box::new(on_show));
75 PrimaryHandle {
76 #[cfg(unix)]
77 app_id: app_id.to_string(),
78 rx,
79 }
80}