win_desktop_utils/
shell.rs1use std::ffi::OsStr;
2use std::io;
3use std::os::windows::ffi::OsStrExt;
4use std::path::Path;
5use std::process::Command;
6
7use windows::core::PCWSTR;
8use windows::Win32::Foundation::HWND;
9use windows::Win32::UI::Shell::{SHFILEOPSTRUCTW, SHFileOperationW, ShellExecuteW};
10use windows::Win32::UI::WindowsAndMessaging::SW_SHOWNORMAL;
11
12use crate::error::{Error, Result};
13
14const FO_DELETE_CODE: u32 = 3;
15const FOF_SILENT: u16 = 0x0004;
16const FOF_NOCONFIRMATION: u16 = 0x0010;
17const FOF_ALLOWUNDO: u16 = 0x0040;
18const FOF_NOERRORUI: u16 = 0x0400;
19
20fn to_wide_os(value: &OsStr) -> Vec<u16> {
21 value.encode_wide().chain(std::iter::once(0)).collect()
22}
23
24fn to_wide_str(value: &str) -> Vec<u16> {
25 OsStr::new(value)
26 .encode_wide()
27 .chain(std::iter::once(0))
28 .collect()
29}
30
31fn to_double_null_path(value: &Path) -> Vec<u16> {
32 value
33 .as_os_str()
34 .encode_wide()
35 .chain(std::iter::once(0))
36 .chain(std::iter::once(0))
37 .collect()
38}
39
40fn shell_open_raw(target: &OsStr) -> Result<()> {
41 let operation = to_wide_str("open");
42 let target_w = to_wide_os(target);
43
44 let result = unsafe {
45 ShellExecuteW(
46 Some(HWND::default()),
47 PCWSTR(operation.as_ptr()),
48 PCWSTR(target_w.as_ptr()),
49 PCWSTR::null(),
50 PCWSTR::null(),
51 SW_SHOWNORMAL,
52 )
53 };
54
55 let code = result.0 as isize;
56 if code <= 32 {
57 Err(Error::WindowsApi {
58 context: "ShellExecuteW",
59 code: code as i32,
60 })
61 } else {
62 Ok(())
63 }
64}
65
66pub fn open_with_default(target: impl AsRef<Path>) -> Result<()> {
73 let path = target.as_ref();
74
75 if path.as_os_str().is_empty() {
76 return Err(Error::InvalidInput("target cannot be empty"));
77 }
78
79 shell_open_raw(path.as_os_str())
80}
81
82pub fn open_url(url: &str) -> Result<()> {
92 if url.trim().is_empty() {
93 return Err(Error::InvalidInput("url cannot be empty"));
94 }
95
96 shell_open_raw(OsStr::new(url))
97}
98
99pub fn reveal_in_explorer(path: impl AsRef<Path>) -> Result<()> {
108 let path = path.as_ref();
109
110 if path.as_os_str().is_empty() {
111 return Err(Error::InvalidInput("path cannot be empty"));
112 }
113
114 Command::new("explorer.exe")
115 .arg("/select,")
116 .arg(path)
117 .spawn()?;
118
119 Ok(())
120}
121
122pub fn move_to_recycle_bin(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.is_absolute() {
141 return Err(Error::InvalidInput("path must be absolute"));
142 }
143
144 if !path.exists() {
145 return Err(Error::Io(io::Error::new(
146 io::ErrorKind::NotFound,
147 "path does not exist",
148 )));
149 }
150
151 let from_w = to_double_null_path(path);
152
153 let mut op = SHFILEOPSTRUCTW {
154 hwnd: HWND::default(),
155 wFunc: FO_DELETE_CODE,
156 pFrom: PCWSTR(from_w.as_ptr()),
157 pTo: PCWSTR::null(),
158 fFlags: FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
159 fAnyOperationsAborted: false.into(),
160 hNameMappings: std::ptr::null_mut(),
161 lpszProgressTitle: PCWSTR::null(),
162 };
163
164 let result = unsafe { SHFileOperationW(&mut op) };
165
166 if result != 0 {
167 Err(Error::WindowsApi {
168 context: "SHFileOperationW(FO_DELETE)",
169 code: result,
170 })
171 } else if op.fAnyOperationsAborted.as_bool() {
172 Err(Error::WindowsApi {
173 context: "SHFileOperationW(FO_DELETE) aborted",
174 code: 0,
175 })
176 } else {
177 Ok(())
178 }
179}