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