Skip to content

Commit 2e65797

Browse files
authored
feat: ensure only one instance is running (#38)
1 parent 20a86c8 commit 2e65797

File tree

3 files changed

+56
-5
lines changed

3 files changed

+56
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ features = [
2525
"Win32_UI_Accessibility",
2626
"Win32_Graphics_Dwm",
2727
"Win32_Graphics_Gdi",
28+
"Win32_Security",
2829
"Win32_System_LibraryLoader",
2930
"Win32_System_Console",
3031
"Win32_System_Registry",

src/main.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
#![windows_subsystem = "windows"]
22

3-
use anyhow::{anyhow, Result};
3+
use anyhow::{anyhow, bail, Result};
44
use std::{
55
fs::{File, OpenOptions},
66
path::Path,
77
};
88

99
use ini::Ini;
10-
use window_switcher::{alert, start, utils::get_exe_folder, Config};
10+
use window_switcher::{
11+
alert, start,
12+
utils::{get_exe_folder, SingleInstance},
13+
Config,
14+
};
1115

1216
fn main() {
1317
if let Err(err) = run() {
@@ -27,6 +31,10 @@ fn run() -> Result<()> {
2731
})?;
2832
simple_logging::log_to(file, config.log_level);
2933
}
34+
let instance = SingleInstance::create("WindowSwitcherMutex")?;
35+
if !instance.is_single() {
36+
bail!("Another instance is running. This instance will abort.")
37+
}
3038
start(&config)
3139
}
3240

src/utils.rs

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use anyhow::{anyhow, Result};
22
use indexmap::IndexMap;
33
use windows::core::{Error, PCWSTR, PWSTR};
4-
use windows::Win32::Foundation::{SetLastError, BOOL, ERROR_SUCCESS, HANDLE, HWND, LPARAM};
4+
use windows::Win32::Foundation::{
5+
CloseHandle, SetLastError, BOOL, ERROR_ALREADY_EXISTS, ERROR_SUCCESS, HANDLE, HWND, LPARAM,
6+
};
57
use windows::Win32::Graphics::Dwm::{DwmGetWindowAttribute, DWMWA_CLOAKED, DWM_CLOAKED_SHELL};
68
use windows::Win32::System::Console::{AllocConsole, FreeConsole, GetConsoleWindow};
79
use windows::Win32::System::LibraryLoader::GetModuleFileNameW;
810
use windows::Win32::System::Threading::{
9-
OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, PROCESS_QUERY_INFORMATION,
10-
PROCESS_VM_READ,
11+
CreateMutexW, OpenProcess, QueryFullProcessImageNameW, ReleaseMutex, PROCESS_NAME_WIN32,
12+
PROCESS_QUERY_INFORMATION, PROCESS_VM_READ,
1113
};
1214
use windows::Win32::UI::Controls::STATE_SYSTEM_INVISIBLE;
1315
use windows::Win32::UI::Input::KeyboardAndMouse::{RegisterHotKey, UnregisterHotKey, MOD_NOREPEAT};
@@ -321,3 +323,43 @@ impl CheckError for u16 {
321323
pub fn to_wstring(value: &str) -> Vec<u16> {
322324
value.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
323325
}
326+
327+
/// A struct representing one running instance.
328+
pub struct SingleInstance {
329+
handle: Option<HANDLE>,
330+
}
331+
332+
unsafe impl Send for SingleInstance {}
333+
unsafe impl Sync for SingleInstance {}
334+
335+
impl SingleInstance {
336+
/// Returns a new SingleInstance object.
337+
pub fn create(name: &str) -> Result<Self> {
338+
let name = to_wstring(name);
339+
let handle = unsafe { CreateMutexW(None, BOOL(1), PCWSTR(name.as_ptr())) }
340+
.map_err(|err| anyhow!("Fail to setup single instance, {err}"))?;
341+
let handle =
342+
if windows::core::Error::from_win32().code() == ERROR_ALREADY_EXISTS.to_hresult() {
343+
None
344+
} else {
345+
Some(handle)
346+
};
347+
Ok(SingleInstance { handle })
348+
}
349+
350+
/// Returns whether this instance is single.
351+
pub fn is_single(&self) -> bool {
352+
self.handle.is_some()
353+
}
354+
}
355+
356+
impl Drop for SingleInstance {
357+
fn drop(&mut self) {
358+
if let Some(handle) = self.handle.take() {
359+
unsafe {
360+
ReleaseMutex(handle);
361+
CloseHandle(handle);
362+
}
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)