blob: bd0958f33534de1ddecdede5fb42ba7c14ed4c78 [file] [log] [blame]
use super::convert_path;
use crate::OpenError;
use normpath::PathExt;
use std::path::Path;
use std::{io, ptr, thread};
use winapi::shared::minwindef::{DWORD, UINT};
use winapi::shared::ntdef::PCWSTR;
use winapi::shared::winerror::HRESULT;
use winapi::um::combaseapi::{CoInitializeEx, CoUninitialize};
use winapi::um::objbase::COINIT_MULTITHREADED;
use winapi::um::shtypes::{
PCIDLIST_ABSOLUTE, PCUITEMID_CHILD_ARRAY, PIDLIST_ABSOLUTE, PIDLIST_RELATIVE,
};
pub(crate) fn reveal(path: &Path) -> Result<(), OpenError> {
let path = path.to_owned();
thread::Builder::new()
.spawn(move || reveal_in_thread(&path).map_err(OpenError::Io))
.map_err(OpenError::Io)?
.join()
.expect("COM worker thread should not panic")
}
fn reveal_in_thread(path: &Path) -> io::Result<()> {
to_io_result(unsafe { CoInitializeEx(ptr::null_mut(), COINIT_MULTITHREADED) })?;
let item_id_list = ItemIdList::new(path)?;
// Because the cidl argument is zero, SHOpenFolderAndSelectItems opens the singular item
// in our item id list in its containing folder and selects it.
to_io_result(unsafe { SHOpenFolderAndSelectItems(item_id_list.0, 0, ptr::null(), 0) })?;
unsafe { CoUninitialize() };
Ok(())
}
fn to_io_result(hresult: HRESULT) -> io::Result<()> {
if hresult >= 0 {
Ok(())
} else {
// See the HRESULT_CODE macro in winerror.h
Err(io::Error::from_raw_os_error(hresult & 0xFFFF))
}
}
struct ItemIdList(PIDLIST_ABSOLUTE);
impl ItemIdList {
fn new(path: &Path) -> io::Result<Self> {
// The ILCreateFromPathW function expects a canonicalized path.
// Unfortunately it does not support NT UNC paths (which std::path::canonicalize returns)
// so we use the normpath crate instead.
let path = convert_path(path.normalize()?.as_os_str())?;
let result = unsafe { ILCreateFromPathW(path.as_ptr()) };
if result.is_null() {
Err(io::Error::last_os_error())
} else {
Ok(ItemIdList(result))
}
}
}
impl Drop for ItemIdList {
fn drop(&mut self) {
unsafe { ILFree(self.0) }
}
}
#[link(name = "Shell32")]
extern "C" {
fn ILCreateFromPathW(pszPath: PCWSTR) -> PIDLIST_ABSOLUTE;
fn ILFree(pidl: PIDLIST_RELATIVE);
fn SHOpenFolderAndSelectItems(
pidlFolder: PCIDLIST_ABSOLUTE,
cidl: UINT,
apidl: PCUITEMID_CHILD_ARRAY,
dwFlags: DWORD,
) -> HRESULT;
}