win_desktop_utils/
instance.rs1use 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#[must_use = "keep this guard alive for as long as you want to hold the single-instance lock"]
14#[derive(Debug)]
15pub struct InstanceGuard {
16 handle: HANDLE,
17}
18
19impl Drop for InstanceGuard {
20 fn drop(&mut self) {
21 unsafe {
22 let _ = CloseHandle(self.handle);
23 }
24 }
25}
26
27fn to_wide_str(value: &str) -> Vec<u16> {
28 OsStr::new(value)
29 .encode_wide()
30 .chain(std::iter::once(0))
31 .collect()
32}
33
34#[must_use = "store the returned guard for as long as the process should be considered the active instance"]
59pub fn single_instance(app_id: &str) -> Result<Option<InstanceGuard>> {
60 if app_id.trim().is_empty() {
61 return Err(Error::InvalidInput("app_id cannot be empty"));
62 }
63
64 let mutex_name = format!("Local\\win_desktop_utils_{app_id}");
65 let mutex_name_w = to_wide_str(&mutex_name);
66
67 let handle =
68 unsafe { CreateMutexW(None, false, PCWSTR(mutex_name_w.as_ptr())) }.map_err(|e| {
69 Error::WindowsApi {
70 context: "CreateMutexW",
71 code: e.code().0,
72 }
73 })?;
74
75 let last_error = unsafe { GetLastError() };
76
77 if last_error == ERROR_ALREADY_EXISTS {
78 unsafe {
79 let _ = CloseHandle(handle);
80 }
81 Ok(None)
82 } else {
83 Ok(Some(InstanceGuard { handle }))
84 }
85}