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::{SHFileOperationW, ShellExecuteW, SHFILEOPSTRUCTW};
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<()> {
68 let path = target.as_ref();
69
70 if path.as_os_str().is_empty() {
71 return Err(Error::InvalidInput("target cannot be empty"));
72 }
73
74 shell_open_raw(path.as_os_str())
75}
76
77pub fn open_url(url: &str) -> Result<()> {
79 if url.trim().is_empty() {
80 return Err(Error::InvalidInput("url cannot be empty"));
81 }
82
83 shell_open_raw(OsStr::new(url))
84}
85
86pub fn reveal_in_explorer(path: impl AsRef<Path>) -> Result<()> {
88 let path = path.as_ref();
89
90 if path.as_os_str().is_empty() {
91 return Err(Error::InvalidInput("path cannot be empty"));
92 }
93
94 let arg = format!("/select,{}", path.display());
95
96 Command::new("explorer.exe").arg(arg).spawn()?;
97
98 Ok(())
99}
100
101pub fn move_to_recycle_bin(path: impl AsRef<Path>) -> Result<()> {
105 let path = path.as_ref();
106
107 if path.as_os_str().is_empty() {
108 return Err(Error::InvalidInput("path cannot be empty"));
109 }
110
111 if !path.is_absolute() {
112 return Err(Error::InvalidInput("path must be absolute"));
113 }
114
115 if !path.exists() {
116 return Err(Error::Io(io::Error::new(
117 io::ErrorKind::NotFound,
118 "path does not exist",
119 )));
120 }
121
122 let from_w = to_double_null_path(path);
123
124 let mut op = SHFILEOPSTRUCTW {
125 hwnd: HWND::default(),
126 wFunc: FO_DELETE_CODE,
127 pFrom: PCWSTR(from_w.as_ptr()),
128 pTo: PCWSTR::null(),
129 fFlags: FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
130 fAnyOperationsAborted: false.into(),
131 hNameMappings: std::ptr::null_mut(),
132 lpszProgressTitle: PCWSTR::null(),
133 };
134
135 let result = unsafe { SHFileOperationW(&mut op) };
136
137 if result != 0 {
138 Err(Error::WindowsApi {
139 context: "SHFileOperationW(FO_DELETE)",
140 code: result,
141 })
142 } else if op.fAnyOperationsAborted.as_bool() {
143 Err(Error::WindowsApi {
144 context: "SHFileOperationW(FO_DELETE) aborted",
145 code: 0,
146 })
147 } else {
148 Ok(())
149 }
150}