win_desktop_utils/lib.rs
1//! Windows desktop helpers for Rust apps.
2//!
3//! `win-desktop-utils` wraps a focused set of Windows desktop chores that many
4//! GUI, tray, installer-adjacent, and local utility apps need, while keeping raw
5//! Win32 shell, shortcut, mutex, known-folder, and elevation APIs out of the
6//! application code that uses them.
7//!
8//! The low-level helpers stay available as small functions, and [`DesktopApp`]
9//! provides a friendlier facade for common app startup and app-data workflows.
10//! On non-Windows targets, the public API still compiles and operational helpers
11//! return [`Error::Unsupported`].
12//!
13//! # When To Use This
14//!
15//! Use this crate when a Windows desktop app needs to:
16//!
17//! - [`open_with_default`]
18//! - [`open_with_verb`]
19//! - [`show_properties`]
20//! - [`print_with_default`]
21//! - [`open_url`]
22//! - [`reveal_in_explorer`]
23//! - [`open_containing_folder`]
24//! - [`move_to_recycle_bin`]
25//! - [`move_paths_to_recycle_bin`]
26//! - [`empty_recycle_bin`]
27//! - [`empty_recycle_bin_for_root`]
28//! - [`create_shortcut`]
29//! - [`create_url_shortcut`]
30//! - [`single_instance`]
31//! - [`single_instance_with_scope`]
32//! - [`single_instance_with_options`]
33//! - [`roaming_app_data`]
34//! - [`local_app_data`]
35//! - [`ensure_roaming_app_data`]
36//! - [`ensure_local_app_data`]
37//! - [`is_elevated`]
38//! - [`restart_as_admin`]
39//! - [`run_as_admin`]
40//! - [`run_with_verb`]
41//! - [`InstanceScope`]
42//! - [`SingleInstanceOptions`]
43//! - [`DesktopApp`]
44//! - [`ShortcutOptions`]
45//! - [`ShortcutIcon`]
46//!
47//! This crate is Windows-first. Non-Windows builds provide stubs so cross-platform
48//! applications can depend on the crate without wrapping the dependency itself in
49//! `cfg(windows)`.
50//!
51//! # Quick Start
52//!
53//! ```
54//! fn main() -> Result<(), win_desktop_utils::Error> {
55//! let app = win_desktop_utils::DesktopApp::new(format!(
56//! "demo-app-{}",
57//! std::process::id()
58//! ))?;
59//!
60//! let _guard = match app.single_instance()? {
61//! Some(guard) => guard,
62//! None => {
63//! println!("already running");
64//! return Ok(());
65//! }
66//! };
67//!
68//! let local = app.ensure_local_data_dir()?;
69//! assert!(local.exists());
70//!
71//! Ok(())
72//! }
73//! ```
74//!
75//! # Feature Flags
76//!
77//! Default features enable the full API. Consumers can opt out and select only
78//! the modules they need:
79//!
80//! ```toml
81//! [dependencies]
82//! win-desktop-utils = { version = "0.5", default-features = false, features = ["paths", "instance"] }
83//! ```
84//!
85//! Cross-platform applications can also keep the dependency Windows-only:
86//!
87//! ```toml
88//! [target.'cfg(windows)'.dependencies]
89//! win-desktop-utils = "0.5"
90//! ```
91//!
92//! Available features:
93//!
94//! - `app`: [`DesktopApp`] facade for app-data and single-instance startup.
95//! - `paths`: per-user local and roaming app-data helpers.
96//! - `instance`: named-mutex single-instance helpers.
97//! - `shell`: shell opening, URL, Explorer, and shell-verb helpers.
98//! - `recycle-bin`: Recycle Bin move and empty helpers.
99//! - `shortcuts`: `.lnk` and `.url` shortcut helpers.
100//! - `elevation`: elevation detection and shell-based relaunch helpers.
101//!
102//! The default feature set is intentionally broad for convenience. Feature flags
103//! control this crate's public modules; the underlying `windows` dependency uses
104//! one shared set of Win32 bindings when any Windows API feature is enabled.
105//!
106//! # Common Workflows
107//!
108//! Startup guard plus app-data directory:
109//!
110//! ```
111//! let app = win_desktop_utils::DesktopApp::new(format!(
112//! "workflow-demo-{}",
113//! std::process::id()
114//! ))?;
115//! let _guard = app.single_instance()?.expect("first instance");
116//! let config_dir = app.ensure_local_data_dir()?;
117//! assert!(config_dir.exists());
118//! # Ok::<(), win_desktop_utils::Error>(())
119//! ```
120//!
121//! Create a shortcut:
122//!
123//! ```no_run
124//! let shortcut = std::env::current_dir()?.join("notepad.lnk");
125//! let options = win_desktop_utils::ShortcutOptions::new()
126//! .description("Open Notepad");
127//! win_desktop_utils::create_shortcut(&shortcut, r"C:\Windows\notepad.exe", &options)?;
128//! # Ok::<(), Box<dyn std::error::Error>>(())
129//! ```
130//!
131//! Relaunch the current executable as administrator:
132//!
133//! ```no_run
134//! use std::ffi::OsString;
135//!
136//! if !win_desktop_utils::is_elevated()? {
137//! win_desktop_utils::restart_as_admin(&[OsString::from("--elevated")])?;
138//! }
139//! # Ok::<(), win_desktop_utils::Error>(())
140//! ```
141//!
142//! # Behavior And Side Effects
143//!
144//! - [`open_with_default`] requires a non-empty existing path.
145//! - [`open_with_verb`] uses `ShellExecuteW` with the requested shell verb.
146//! - [`show_properties`] and [`print_with_default`] are small shell-verb wrappers.
147//! - [`open_url`] trims surrounding whitespace before delegating to the Windows shell.
148//! - [`reveal_in_explorer`] requires an existing path and launches `explorer.exe`.
149//! - [`open_containing_folder`] opens the existing path's parent directory.
150//! - [`move_to_recycle_bin`] requires an absolute existing path and uses `IFileOperation`
151//! on a dedicated STA thread for recycle-bin behavior.
152//! - [`move_paths_to_recycle_bin`] validates all paths before starting one shell
153//! recycle-bin operation.
154//! - [`empty_recycle_bin`] permanently empties the Recycle Bin without showing shell UI.
155//! - [`create_shortcut`] uses `IShellLinkW` on a dedicated STA thread.
156//! - [`roaming_app_data`] and [`local_app_data`] resolve their base directories via
157//! `SHGetKnownFolderPath`.
158//! - [`single_instance`] uses a `Local\...` named mutex, so the lock is scoped to the
159//! current Windows session, and `app_id` cannot contain backslashes.
160//! - [`single_instance_with_scope`] can opt into either the current-session or global
161//! mutex namespace.
162//! - [`restart_as_admin`] starts a new elevated instance of the current executable,
163//! does not terminate the current process, and rejects arguments containing NUL bytes.
164//! - [`run_as_admin`] and [`run_with_verb`] launch arbitrary commands through
165//! `ShellExecuteW`.
166//!
167//! # Stability
168//!
169//! The minimum supported Rust version is 1.82. Public API compatibility is checked
170//! in CI with `cargo-semver-checks`, and dependency policy is checked with
171//! `cargo-deny`.
172
173#![warn(missing_docs)]
174#![warn(rustdoc::bare_urls)]
175#![warn(rustdoc::broken_intra_doc_links)]
176
177#[cfg(all(windows, feature = "app"))]
178pub mod app;
179#[cfg(all(windows, feature = "elevation"))]
180pub mod elevation;
181pub mod error;
182#[cfg(all(windows, feature = "instance"))]
183pub mod instance;
184#[cfg(all(windows, feature = "paths"))]
185pub mod paths;
186#[cfg(all(windows, any(feature = "shell", feature = "recycle-bin")))]
187pub mod shell;
188#[cfg(all(windows, feature = "shortcuts"))]
189pub mod shortcuts;
190#[cfg(all(
191 not(windows),
192 any(
193 feature = "app",
194 feature = "elevation",
195 feature = "instance",
196 feature = "paths",
197 feature = "recycle-bin",
198 feature = "shell",
199 feature = "shortcuts",
200 )
201))]
202mod unsupported;
203#[cfg(all(
204 windows,
205 any(
206 feature = "elevation",
207 feature = "instance",
208 feature = "recycle-bin",
209 feature = "shell",
210 feature = "shortcuts",
211 )
212))]
213mod win;
214
215pub use error::{Error, Result};
216
217#[cfg(all(windows, feature = "app"))]
218pub use app::DesktopApp;
219#[cfg(all(windows, feature = "elevation"))]
220pub use elevation::{is_elevated, restart_as_admin, run_as_admin, run_with_verb};
221#[cfg(all(windows, feature = "instance"))]
222pub use instance::{
223 single_instance, single_instance_with_options, single_instance_with_scope, InstanceGuard,
224 InstanceScope, SingleInstanceOptions,
225};
226#[cfg(all(windows, feature = "paths"))]
227pub use paths::{ensure_local_app_data, ensure_roaming_app_data, local_app_data, roaming_app_data};
228#[cfg(all(windows, feature = "recycle-bin"))]
229pub use shell::{
230 empty_recycle_bin, empty_recycle_bin_for_root, move_paths_to_recycle_bin, move_to_recycle_bin,
231};
232#[cfg(all(windows, feature = "shell"))]
233pub use shell::{
234 open_containing_folder, open_url, open_with_default, open_with_verb, print_with_default,
235 reveal_in_explorer, show_properties,
236};
237#[cfg(all(windows, feature = "shortcuts"))]
238pub use shortcuts::{create_shortcut, create_url_shortcut, ShortcutIcon, ShortcutOptions};
239#[cfg(all(not(windows), feature = "app"))]
240pub use unsupported::DesktopApp;
241#[cfg(all(not(windows), feature = "shortcuts"))]
242pub use unsupported::{create_shortcut, create_url_shortcut, ShortcutIcon, ShortcutOptions};
243#[cfg(all(not(windows), feature = "recycle-bin"))]
244pub use unsupported::{
245 empty_recycle_bin, empty_recycle_bin_for_root, move_paths_to_recycle_bin, move_to_recycle_bin,
246};
247#[cfg(all(not(windows), feature = "paths"))]
248pub use unsupported::{
249 ensure_local_app_data, ensure_roaming_app_data, local_app_data, roaming_app_data,
250};
251#[cfg(all(not(windows), feature = "elevation"))]
252pub use unsupported::{is_elevated, restart_as_admin, run_as_admin, run_with_verb};
253#[cfg(all(not(windows), feature = "shell"))]
254pub use unsupported::{
255 open_containing_folder, open_url, open_with_default, open_with_verb, print_with_default,
256 reveal_in_explorer, show_properties,
257};
258#[cfg(all(not(windows), feature = "instance"))]
259pub use unsupported::{
260 single_instance, single_instance_with_options, single_instance_with_scope, InstanceGuard,
261 InstanceScope, SingleInstanceOptions,
262};