use std::path::{Path, PathBuf};
use windows::Win32::System::Com::CoTaskMemAlloc;
use windows::Win32::UI::Shell::Common::ITEMIDLIST;
use windows::Win32::UI::Shell::{IShellFolder, SHBindToObject, SHBindToParent, SHParseDisplayName};
use crate::com::Pidl;
use crate::error::{Error, Result};
use crate::util::{path_to_wide, strip_extended_prefix, wide_to_pcwstr};
pub struct ShellItems {
pub(crate) parent: IShellFolder,
pub(crate) child_pidls: Vec<Pidl>,
pub(crate) _absolute_pidls: Vec<Pidl>,
pub(crate) is_background: bool,
}
impl ShellItems {
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
Self::from_paths(&[path.as_ref().to_path_buf()])
}
pub fn from_paths(paths: &[impl AsRef<Path>]) -> Result<Self> {
if paths.is_empty() {
return Err(Error::NoCommonParent);
}
let mut absolute_pidls = Vec::with_capacity(paths.len());
let mut child_pidls = Vec::with_capacity(paths.len());
let mut parent_folder: Option<IShellFolder> = None;
let mut parent_path: Option<PathBuf> = None;
for path in paths {
let path = path.as_ref();
let canonical = std::fs::canonicalize(path)
.map(|p| strip_extended_prefix(&p))
.unwrap_or_else(|_| path.to_path_buf());
let this_parent = canonical.parent().map(|p| p.to_path_buf());
match (&parent_path, &this_parent) {
(Some(existing), Some(new)) if existing != new => {
return Err(Error::NoCommonParent);
}
(None, Some(_)) => {
parent_path = this_parent;
}
_ => {}
}
let wide = path_to_wide(&canonical);
let pcwstr = wide_to_pcwstr(&wide);
let mut abs_pidl: *mut ITEMIDLIST = std::ptr::null_mut();
unsafe {
SHParseDisplayName(pcwstr, None, &mut abs_pidl, 0, None).map_err(|e| {
Error::ParsePath {
path: canonical.clone(),
source: e,
}
})?;
}
let abs_pidl = unsafe { Pidl::from_raw(abs_pidl) };
let mut child_pidl_ptr: *mut ITEMIDLIST = std::ptr::null_mut();
let shell_folder: IShellFolder = unsafe {
let mut child_pidl_raw: *mut ITEMIDLIST = std::ptr::null_mut();
let folder: IShellFolder =
SHBindToParent(abs_pidl.as_ptr(), Some(&mut child_pidl_raw))
.map_err(Error::BindToParent)?;
let child_size = pidl_size(child_pidl_raw as *const _);
if child_size > 0 {
let alloc = CoTaskMemAlloc(child_size + 2);
if !alloc.is_null() {
std::ptr::copy_nonoverlapping(
child_pidl_raw as *const u8,
alloc as *mut u8,
child_size,
);
std::ptr::write_bytes((alloc as *mut u8).add(child_size), 0, 2);
child_pidl_ptr = alloc as *mut ITEMIDLIST;
}
}
folder
};
if parent_folder.is_none() {
parent_folder = Some(shell_folder);
}
child_pidls.push(unsafe { Pidl::from_raw(child_pidl_ptr) });
absolute_pidls.push(abs_pidl);
}
Ok(Self {
parent: parent_folder.unwrap(),
child_pidls,
_absolute_pidls: absolute_pidls,
is_background: false,
})
}
pub fn folder_background(folder: impl AsRef<Path>) -> Result<Self> {
let folder = folder.as_ref();
let canonical = std::fs::canonicalize(folder)
.map(|p| strip_extended_prefix(&p))
.unwrap_or_else(|_| folder.to_path_buf());
let wide = path_to_wide(&canonical);
let pcwstr = wide_to_pcwstr(&wide);
let mut abs_pidl: *mut ITEMIDLIST = std::ptr::null_mut();
unsafe {
SHParseDisplayName(pcwstr, None, &mut abs_pidl, 0, None).map_err(|e| {
Error::ParsePath {
path: canonical.clone(),
source: e,
}
})?;
}
let abs_pidl = unsafe { Pidl::from_raw(abs_pidl) };
let folder_shell: IShellFolder = unsafe {
SHBindToObject(None, abs_pidl.as_ptr(), None).map_err(Error::BindToParent)?
};
Ok(Self {
parent: folder_shell,
child_pidls: Vec::new(),
_absolute_pidls: vec![abs_pidl],
is_background: true,
})
}
}
unsafe fn pidl_size(pidl: *const ITEMIDLIST) -> usize {
if pidl.is_null() {
return 0;
}
let mut size = 0usize;
let mut ptr = pidl as *const u8;
loop {
let cb = unsafe { (ptr as *const u16).read_unaligned() };
if cb == 0 {
break;
}
size += cb as usize;
ptr = unsafe { ptr.add(cb as usize) };
}
size
}