use super::*;
use std::fs;
use std::os::raw::c_void;
use windows::core::HSTRING;
use windows::Win32::System::Com::IBindCtx;
use windows::Win32::UI::Shell::{self, Common::ITEMIDLIST};
fn official_hr(path: &Path) -> u32 {
let name = wide_name(path);
unsafe {
let mut pidl: *mut ITEMIDLIST = ptr::null_mut();
let result = Shell::SHParseDisplayName(&name, None::<&IBindCtx>, &mut pidl, 0, None);
let hr = match result {
Ok(()) => 0,
Err(e) => e.code().0,
};
Shell::ILFree(Some(pidl));
hr as u32
}
}
fn our_hr(path: &Path) -> u32 {
let wide = encode(path);
unsafe {
let mut pidl = ptr::null_mut();
let hr = ffi::native::SHParseDisplayName(wide.as_ptr(), ptr::null(), &mut pidl, 0, ptr::null_mut());
ffi::native::ILFree(pidl);
hr as u32
}
}
fn wide_name(path: &Path) -> HSTRING {
let text: Vec<u16> = path.as_os_str().encode_wide().collect();
HSTRING::from_wide(&text)
}
fn code_of(outcome: windows::core::Result<()>) -> u32 {
match outcome {
Ok(()) => 0,
Err(error) => error.code().0 as u32,
}
}
fn encode(path: &Path) -> Vec<u16> {
path.as_os_str().encode_wide().chain([0]).collect()
}
fn verbatim(path: &Path) -> PathBuf {
PathBuf::from(OsString::from(format!(r"\\?\{}", path.display())))
}
unsafe fn item_bytes(id: *const c_void) -> Vec<u8> {
let size = *(id as *const u16) as usize;
std::slice::from_raw_parts(id as *const u8, size).to_vec()
}
struct Com;
impl Com {
fn init() -> Com {
let hr = unsafe { ffi::native::CoInitializeEx(ptr::null(), COINIT_APARTMENTTHREADED) };
assert!(hr >= 0, "CoInitializeEx failed: 0x{hr:08X}");
Com
}
}
impl Drop for Com {
fn drop(&mut self) {
unsafe { ffi::native::CoUninitialize() };
}
}
fn fixture(tag: &str, name: &str) -> (PathBuf, PathBuf) {
let dir = std::env::temp_dir().join(format!(
"reveal-xcheck-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
fs::create_dir_all(&dir).unwrap();
let file = dir.join(name);
fs::write(&file, "x").unwrap();
(dir, file)
}
#[test]
fn official_bindings_reject_verbatim_paths_exactly_like_ours() {
let _com = Com::init();
let (dir, file) = fixture("verbatim", r"空格 中文,file.txt");
let extended = verbatim(&file);
assert_eq!(our_hr(&extended), 0x8007_0057, "our declaration changed the failure");
assert_eq!(
official_hr(&extended),
0x8007_0057,
"the official binding disagrees about the verbatim form"
);
let plain = normalize(&extended);
assert_eq!(&*plain, file.as_path());
assert_eq!(our_hr(&plain), 0, "{}", plain.display());
assert_eq!(official_hr(&plain), 0, "official binding disagrees after the rewrite");
fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn our_parent_child_split_matches_the_official_helpers() {
let _com = Com::init();
let (dir, file) = fixture("split", "target 文件.txt");
unsafe {
let name = wide_name(&file);
let mut full: *mut ITEMIDLIST = ptr::null_mut();
Shell::SHParseDisplayName(&name, None::<&IBindCtx>, &mut full, 0, None).unwrap();
let ours = Shell::ILClone(full);
let our_child = item_bytes(ffi::native::ILFindLastID(ours as *mut c_void));
ffi::native::ILRemoveLastID(ours as *mut c_void);
assert!(
Shell::ILIsParent(ours, full, true).as_bool(),
"our ILRemoveLastID did not leave the direct parent of the item"
);
assert!(
Shell::ILIsParent(ours, full, false).as_bool(),
"our truncated list is not even an ancestor of the item"
);
assert_eq!(
our_child,
item_bytes(Shell::ILFindLastID(full) as *const c_void),
"our ILFindLastID does not return the official last id"
);
Shell::ILFree(Some(full));
ffi::native::ILFree(ours as *mut c_void);
}
fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn both_bindings_refuse_a_null_folder_before_opening_one() {
let _com = Com::init();
let ours = unsafe { ffi::native::SHOpenFolderAndSelectItems(ptr::null(), 0, ptr::null(), 0) };
let theirs = unsafe {
Shell::SHOpenFolderAndSelectItems(ptr::null(), None::<&[*const ITEMIDLIST]>, 0u32)
};
assert_eq!(ours as u32, 0x8007_0057, "the null folder no longer fails the way it measured");
assert_eq!(
code_of(theirs),
0x8007_0057,
"the official binding marshals the null folder to a different failure"
);
}
#[test]
fn both_bindings_free_a_null_pidl_without_crashing() {
let _com = Com::init();
unsafe { ffi::native::ILFree(ptr::null_mut()) };
unsafe { Shell::ILFree(None) };
}