Skip to main content

win_desktop_utils/
instance.rs

1use std::ffi::OsStr;
2use std::os::windows::ffi::OsStrExt;
3
4use windows::core::PCWSTR;
5use windows::Win32::Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, HANDLE};
6use windows::Win32::System::Threading::CreateMutexW;
7
8use crate::error::{Error, Result};
9
10/// Guard that keeps the named single-instance mutex alive for the current process.
11///
12/// Dropping this value releases the underlying mutex handle.
13#[derive(Debug)]
14pub struct InstanceGuard {
15    handle: HANDLE,
16}
17
18impl Drop for InstanceGuard {
19    fn drop(&mut self) {
20        unsafe {
21            let _ = CloseHandle(self.handle);
22        }
23    }
24}
25
26fn to_wide_str(value: &str) -> Vec<u16> {
27    OsStr::new(value)
28        .encode_wide()
29        .chain(std::iter::once(0))
30        .collect()
31}
32
33/// Attempts to acquire a named process-wide single-instance guard.
34///
35/// Returns `Ok(Some(InstanceGuard))` for the first instance and `Ok(None)` if another
36/// instance with the same `app_id` is already running.
37///
38/// The mutex name is derived from `app_id` using a `Local\...` namespace, so the
39/// single-instance behavior is scoped to the current Windows session.
40///
41/// # Errors
42///
43/// Returns [`Error::InvalidInput`] if `app_id` is empty or whitespace only.
44/// Returns [`Error::WindowsApi`] if `CreateMutexW` fails.
45pub fn single_instance(app_id: &str) -> Result<Option<InstanceGuard>> {
46    if app_id.trim().is_empty() {
47        return Err(Error::InvalidInput("app_id cannot be empty"));
48    }
49
50    let mutex_name = format!("Local\\win_desktop_utils_{app_id}");
51    let mutex_name_w = to_wide_str(&mutex_name);
52
53    let handle =
54        unsafe { CreateMutexW(None, false, PCWSTR(mutex_name_w.as_ptr())) }.map_err(|e| {
55            Error::WindowsApi {
56                context: "CreateMutexW",
57                code: e.code().0,
58            }
59        })?;
60
61    let last_error = unsafe { GetLastError() };
62
63    if last_error == ERROR_ALREADY_EXISTS {
64        unsafe {
65            let _ = CloseHandle(handle);
66        }
67        Ok(None)
68    } else {
69        Ok(Some(InstanceGuard { handle }))
70    }
71}