1use std::path::Path;
35
36#[cfg(not(any(
37 all(feature = "rustbus", not(feature = "zbus"), not(feature = "gio")),
38 all(not(feature = "rustbus"), feature = "zbus", not(feature = "gio")),
39 all(not(feature = "rustbus"), not(feature = "zbus"), feature = "gio")
40)))]
41compile_error!("only one of `rustbus`, `zbus`, or `gio` must be selected");
42
43#[cfg_attr(target_os = "macos", link(name = "AppKit", kind = "framework"))]
44extern "C" {}
45
46#[cfg(target_os = "macos")]
47use objc::{class, msg_send, sel, sel_impl};
48#[cfg(target_os = "macos")]
49#[allow(non_camel_case_types)]
50type id = *mut objc::runtime::Object;
51#[cfg(target_os = "macos")]
52#[allow(non_upper_case_globals)]
53const nil: id = std::ptr::null_mut();
54
55#[cfg(target_os = "macos")]
56unsafe fn show_nsurl_in_file_manager(nsurl: id) {
57 let ws: id = msg_send![class!(NSWorkspace), sharedWorkspace];
58 let urls: id = msg_send![class!(NSArray), arrayWithObject:nsurl];
59 let urls: id = msg_send![urls, autorelease];
60 let _: () = msg_send![ws, activateFileViewerSelectingURLs:urls];
61}
62
63#[cfg(all(not(target_os = "macos"), not(windows), feature = "gio"))]
64unsafe fn gdbus_show_uri_in_file_manager(uri: *const std::ffi::c_char) {
65 use std::ptr::{null, null_mut};
66
67 let bus = gio_sys::g_bus_get_sync(gio_sys::G_BUS_TYPE_SESSION, null_mut(), null_mut());
68 if bus.is_null() {
69 return;
70 }
71 let uris = [uri, null()];
72 let args = glib_sys::g_variant_new(
73 b"(^ass)\0".as_ptr() as *const _,
74 uris.as_ptr(),
75 b"\0".as_ptr(),
76 );
77 let ret = gio_sys::g_dbus_connection_call_sync(
78 bus,
79 b"org.freedesktop.FileManager1\0".as_ptr() as *const _,
80 b"/org/freedesktop/FileManager1\0".as_ptr() as *const _,
81 b"org.freedesktop.FileManager1\0".as_ptr() as *const _,
82 b"ShowItems\0".as_ptr() as *const _,
83 args,
84 null(),
85 0,
86 -1,
87 null_mut(),
88 null_mut(),
89 );
90 if !ret.is_null() {
91 glib_sys::g_variant_unref(ret);
92 }
93 gobject_sys::g_object_unref(bus as *mut _);
94}
95
96pub fn show_path_in_file_manager(path: impl AsRef<Path>) {
110 #[cfg(windows)]
111 unsafe {
112 use std::{
113 borrow::Cow,
114 path::{Component, Prefix},
115 };
116 use windows::{
117 core::{Result, HSTRING},
118 Win32::{System::Com::*, UI::Shell::*},
119 };
120
121 struct ComHandle(());
122 impl ComHandle {
123 fn new() -> Result<Self> {
124 unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED)? };
125 Ok(Self(()))
126 }
127 }
128 impl Drop for ComHandle {
129 fn drop(&mut self) {
130 unsafe {
131 CoUninitialize();
132 }
133 }
134 }
135 std::thread_local! { static COM_HANDLE: Result<ComHandle> = ComHandle::new(); }
136 COM_HANDLE.with(|r| r.as_ref().map(|_| ()).unwrap());
137
138 let path = Cow::Borrowed(path.as_ref());
139
140 let mut components = path.components();
142 let path = match components.next() {
143 Some(Component::Prefix(prefix)) => match prefix.kind() {
144 Prefix::VerbatimUNC(server, share) => Cow::Owned(
145 Path::new("\\\\").join(Path::new(server).join(share).join(components)),
146 ),
147 Prefix::VerbatimDisk(disk) => {
148 let prefix = [disk, b':', b'\\'];
149 let prefix = std::ffi::OsStr::from_encoded_bytes_unchecked(&prefix);
150 Cow::Owned(Path::new(prefix).join(components))
151 }
152 Prefix::Verbatim(prefix) => {
153 Cow::Owned(Path::new("\\\\").join(Path::new(prefix).join(components)))
154 }
155 _ => path,
156 },
157 _ => path,
158 };
159 let mut idlist = std::ptr::null_mut();
160 let res = SHParseDisplayName(
161 &HSTRING::from(path.as_os_str()),
162 None::<&IBindCtx>,
163 &mut idlist,
164 0,
165 None,
166 );
167 if res.is_ok() && !idlist.is_null() {
168 let _ = SHOpenFolderAndSelectItems(idlist, None, 0);
169 CoTaskMemFree(Some(idlist as *const _));
170 }
171 }
172
173 #[cfg(target_os = "macos")]
174 unsafe {
175 let path = path.as_ref().as_os_str().as_encoded_bytes();
176 let s: id = msg_send![class!(NSString), alloc];
177 let s: id = msg_send![
178 s,
179 initWithBytes:path.as_ptr()
180 length:path.len()
181 encoding:4 as id
182 ];
183 let s: id = msg_send![s, autorelease];
184 let url: id = msg_send![class!(NSURL), fileURLWithPath:s];
185 if url != nil {
186 show_nsurl_in_file_manager(url);
187 }
188 }
189
190 #[cfg(all(not(windows), not(target_os = "macos"), not(feature = "gio")))]
191 {
192 use std::path::Component;
193
194 let path = path.as_ref();
195 if path.is_relative() {
196 return;
197 }
198 let mut uri = String::with_capacity(path.as_os_str().as_encoded_bytes().len() + 7);
199 uri.push_str("file://");
200 let mut components = path.components().peekable();
201 if components.peek().is_none() {
202 return;
203 }
204 while let Some(component) = components.next() {
205 match component {
206 Component::RootDir => uri.push('/'),
207 Component::Prefix(_) => return,
208 _ => {
209 let component = component.as_os_str().as_encoded_bytes();
210 uri.push_str(&urlencoding::encode_binary(component));
211 if components.peek().is_some() {
212 uri.push('/');
213 }
214 }
215 }
216 }
217 show_uri_in_file_manager(&uri);
218 }
219
220 #[cfg(all(not(windows), not(target_os = "macos"), feature = "gio"))]
221 unsafe {
222 let path = path.as_ref().as_os_str().as_encoded_bytes().to_vec();
223 let path = std::ffi::CString::new(path).unwrap_or_else(|e| {
224 let pos = e.nul_position();
225 let mut uri = e.into_vec();
226 uri.truncate(pos);
227 std::ffi::CString::new(uri).unwrap()
228 });
229 let file = gio_sys::g_file_new_for_path(path.as_ptr());
230 let uri = gio_sys::g_file_get_uri(file);
231 if !uri.is_null() {
232 if uri.read() != 0 {
233 gdbus_show_uri_in_file_manager(uri);
234 }
235 glib_sys::g_free(uri as *mut _);
236 }
237 gobject_sys::g_object_unref(file as *mut _);
238 }
239}
240
241pub fn show_uri_in_file_manager(uri: impl AsRef<str>) {
256 #[cfg(windows)]
257 show_path_in_file_manager(Path::new(uri.as_ref()));
258
259 #[cfg(target_os = "macos")]
260 unsafe {
261 let uri = uri.as_ref();
262 let s: id = msg_send![class!(NSString), alloc];
263 let s: id = msg_send![
264 s,
265 initWithBytes:uri.as_ptr()
266 length:uri.len()
267 encoding:4 as id
268 ];
269 let s: id = msg_send![s, autorelease];
270 let url: id = msg_send![class!(NSURL), URLWithString:s];
271 if url != nil {
272 show_nsurl_in_file_manager(url);
273 }
274 }
275
276 #[cfg(all(not(target_os = "macos"), not(windows), feature = "rustbus"))]
277 {
278 if let Ok(mut bus) = rustbus::RpcConn::session_conn(rustbus::connection::Timeout::Infinite)
279 {
280 let uri = uri.as_ref();
281 let mut msg = rustbus::MessageBuilder::new()
282 .call("ShowItems")
283 .on("/org/freedesktop/FileManager1")
284 .with_interface("org.freedesktop.FileManager1")
285 .at("org.freedesktop.FileManager1")
286 .build();
287 msg.body.push_param([uri].as_slice()).unwrap();
288 msg.body.push_param("").unwrap();
289 if let Ok(ctx) = bus.send_message(&mut msg) {
290 let _ = ctx.write_all();
291 }
292 drop(bus);
293 }
294 }
295
296 #[cfg(all(not(target_os = "macos"), not(windows), feature = "zbus"))]
297 {
298 let uri = uri.as_ref();
299 if let Ok(bus) = zbus::blocking::Connection::session() {
300 let _ = bus.call_method(
301 Some("org.freedesktop.FileManager1"),
302 "/org/freedesktop/FileManager1",
303 Some("org.freedesktop.FileManager1"),
304 "ShowItems",
305 &([uri].as_slice(), ""),
306 );
307 }
308 }
309
310 #[cfg(all(not(target_os = "macos"), not(windows), feature = "gio"))]
311 unsafe {
312 let uri = uri.as_ref();
313 let uri = std::ffi::CString::new(uri).unwrap_or_else(|e| {
314 let pos = e.nul_position();
315 let mut uri = e.into_vec();
316 uri.truncate(pos);
317 std::ffi::CString::new(uri).unwrap()
318 });
319 gdbus_show_uri_in_file_manager(uri.as_ptr());
320 }
321}