Skip to main content

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}