win_desktop_utils/
shell.rs1use std::ffi::OsStr;
2use std::os::windows::ffi::OsStrExt;
3use std::path::Path;
4use std::process::Command;
5
6use windows::core::PCWSTR;
7use windows::Win32::Foundation::HWND;
8use windows::Win32::UI::Shell::{SHFileOperationW, ShellExecuteW, SHFILEOPSTRUCTW};
9use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
10
11use crate::error::{Error, Result};
12
13const FO_DELETE_CODE: u32 = 3;
14const FOF_SILENT: u16 = 0x0004;
15const FOF_NOCONFIRMATION: u16 = 0x0010;
16const FOF_ALLOWUNDO: u16 = 0x0040;
17const FOF_NOERRORUI: u16 = 0x0400;
18
19fn to_wide_os(value: &OsStr) -> Vec<u16> {
20 value.encode_wide().chain(std::iter::once(0)).collect()
21}
22
23fn to_wide_str(value: &str) -> Vec<u16> {
24 OsStr::new(value)
25 .encode_wide()
26 .chain(std::iter::once(0))
27 .collect()
28}
29
30fn to_double_null_path(value: &Path) -> Vec<u16> {
31 value
32 .as_os_str()
33 .encode_wide()
34 .chain(std::iter::once(0))
35 .chain(std::iter::once(0))
36 .collect()
37}
38
39fn shell_open_raw(target: &OsStr) -> Result<()> {
40 let operation = to_wide_str("open");
41 let target_w = to_wide_os(target);
42
43 let result = unsafe {
44 ShellExecuteW(
45 Some(HWND::default()),
46 PCWSTR(operation.as_ptr()),
47 PCWSTR(target_w.as_ptr()),
48 PCWSTR::null(),
49 PCWSTR::null(),
50 SW_SHOWNORMAL,
51 )
52 };
53
54 let code = result.0 as isize;
55 if code <= 32 {
56 Err(Error::WindowsApi {
57 context: "ShellExecuteW",
58 code: code as i32,
59 })
60 } else {
61 Ok(())
62 }
63}
64
65pub fn open_with_default(target: impl AsRef<Path>) -> Result<()> {
80 let path = target.as_ref();
81
82 if path.as_os_str().is_empty() {
83 return Err(Error::InvalidInput("target cannot be empty"));
84 }
85
86 if !path.exists() {
87 return Err(Error::PathDoesNotExist);
88 }
89
90 shell_open_raw(path.as_os_str())
91}
92
93pub fn open_url(url: &str) -> Result<()> {
110 if url.trim().is_empty() {
111 return Err(Error::InvalidInput("url cannot be empty"));
112 }
113
114 shell_open_raw(OsStr::new(url))
115}
116
117pub fn reveal_in_explorer(path: impl AsRef<Path>) -> Result<()> {
134 let path = path.as_ref();
135
136 if path.as_os_str().is_empty() {
137 return Err(Error::InvalidInput("path cannot be empty"));
138 }
139
140 if !path.exists() {
141 return Err(Error::PathDoesNotExist);
142 }
143
144 Command::new("explorer.exe")
145 .arg("/select,")
146 .arg(path)
147 .spawn()?;
148
149 Ok(())
150}
151
152pub fn move_to_recycle_bin(path: impl AsRef<Path>) -> Result<()> {
174 let path = path.as_ref();
175
176 if path.as_os_str().is_empty() {
177 return Err(Error::InvalidInput("path cannot be empty"));
178 }
179
180 if !path.is_absolute() {
181 return Err(Error::PathNotAbsolute);
182 }
183
184 if !path.exists() {
185 return Err(Error::PathDoesNotExist);
186 }
187
188 let from_w = to_double_null_path(path);
189
190 let mut op = SHFILEOPSTRUCTW {
191 hwnd: HWND::default(),
192 wFunc: FO_DELETE_CODE,
193 pFrom: PCWSTR(from_w.as_ptr()),
194 pTo: PCWSTR::null(),
195 fFlags: FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
196 fAnyOperationsAborted: false.into(),
197 hNameMappings: std::ptr::null_mut(),
198 lpszProgressTitle: PCWSTR::null(),
199 };
200
201 let result = unsafe { SHFileOperationW(&mut op) };
202
203 if result != 0 {
204 Err(Error::WindowsApi {
205 context: "SHFileOperationW(FO_DELETE)",
206 code: result,
207 })
208 } else if op.fAnyOperationsAborted.as_bool() {
209 Err(Error::WindowsApi {
210 context: "SHFileOperationW(FO_DELETE) aborted",
211 code: 0,
212 })
213 } else {
214 Ok(())
215 }
216}