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#[derive(Debug)]
12pub struct InstanceGuard {
13    handle: HANDLE,
14}
15
16impl Drop for InstanceGuard {
17    fn drop(&mut self) {
18        unsafe {
19            let _ = CloseHandle(self.handle);
20        }
21    }
22}
23
24fn to_wide_str(value: &str) -> Vec<u16> {
25    OsStr::new(value)
26        .encode_wide()
27        .chain(std::iter::once(0))
28        .collect()
29}
30
31/// Attempts to acquire a named process-wide single-instance guard.
32///
33/// Returns `Ok(Some(InstanceGuard))` for the first instance and `Ok(None)` if another
34/// instance with the same `app_id` is already running.
35pub fn single_instance(app_id: &str) -> Result<Option<InstanceGuard>> {
36    if app_id.trim().is_empty() {
37        return Err(Error::InvalidInput("app_id cannot be empty"));
38    }
39
40    let mutex_name = format!("Local\\win_desktop_utils_{app_id}");
41    let mutex_name_w = to_wide_str(&mutex_name);
42
43    let handle =
44        unsafe { CreateMutexW(None, false, PCWSTR(mutex_name_w.as_ptr())) }.map_err(|e| {
45            Error::WindowsApi {
46                context: "CreateMutexW",
47                code: e.code().0,
48            }
49        })?;
50
51    let last_error = unsafe { GetLastError() };
52
53    if last_error == ERROR_ALREADY_EXISTS {
54        unsafe {
55            let _ = CloseHandle(handle);
56        }
57        Ok(None)
58    } else {
59        Ok(Some(InstanceGuard { handle }))
60    }
61}