use std::{cell::SyncUnsafeCell, cmp, ffi::c_void, path::Path};
use bon::Builder;
use ib_hook::inline::InlineHook;
use serde::{Deserialize, Serialize};
use tracing::debug;
use windows::{
Win32::{
Foundation::LPARAM,
UI::Shell::{Common::ITEMIDLIST, IShellFolder},
},
core::{HRESULT, Interface},
};
use crate::{
folder::{CompareIDs as CompareIDsParam, ShellFolder},
id_list::ChildIDRef,
prop::column,
};
#[derive(Default, Serialize, Deserialize, Clone, Builder, Debug)]
pub struct FolderHookConfig {
#[cfg(feature = "everything")]
#[builder(default)]
compare_size_from_everything: bool,
}
type CompareIDs =
unsafe extern "system" fn(*mut c_void, LPARAM, *const ITEMIDLIST, *const ITEMIDLIST) -> HRESULT;
pub(crate) struct FolderHook {
compare_ids: Option<InlineHook<CompareIDs>>,
}
impl FolderHook {
pub fn new(config: FolderHookConfig) -> anyhow::Result<Self> {
let folder = IShellFolder::from_fs_any(Default::default())?;
let compare_ids = if config.compare_size_from_everything {
let compare_ids = folder.vtable().CompareIDs;
InlineHook::new_enabled(compare_ids, compare_ids_detour).ok()
} else {
None
};
Ok(Self { compare_ids })
}
}
pub(crate) static HOOK: SyncUnsafeCell<Option<FolderHook>> = SyncUnsafeCell::new(None);
pub fn apply(config: Option<FolderHookConfig>) -> anyhow::Result<()> {
let hook = unsafe { &mut *HOOK.get() };
*hook = match config {
Some(config) => Some(FolderHook::new(config)?),
None => None,
};
Ok(())
}
unsafe extern "system" fn compare_ids_detour(
this: *mut c_void,
lparam: LPARAM,
pidl1: *const ITEMIDLIST,
pidl2: *const ITEMIDLIST,
) -> HRESULT {
let hook = unsafe { &*HOOK.get() }.as_ref().unwrap();
let real =
|| unsafe { hook.compare_ids.as_ref().unwrap().trampoline()(this, lparam, pidl1, pidl2) };
let param = CompareIDsParam::from(lparam);
#[cfg(feature = "everything")]
if param.column == column::FSColumn::Size as u16
{
let folder = unsafe { IShellFolder::from_raw_borrowed(&this) }.unwrap();
let pidl1 = unsafe { ChildIDRef::from_raw(pidl1) };
let pidl2 = unsafe { ChildIDRef::from_raw(pidl2) };
let is_folder1 = folder.is_child_fs_folder(pidl1);
let is_folder2 = folder.is_child_fs_folder(pidl2);
if is_folder1 && is_folder2 {
let compare = || {
let path1 = folder.get_path_of(pidl1).ok()?.to_string();
let path2 = folder.get_path_of(pidl2).ok()?.to_string();
#[cfg(debug_assertions)]
if path1 == path2 {
debug!("same path");
return Some(cmp::Ordering::Equal);
}
let path1 = Path::new(&path1);
let path2 = Path::new(&path2);
let size1 = everything_ipc::folder::size::get_folder_size(path1)
.eager_get_links(true)
.call()
.ok()?;
let size2 = everything_ipc::folder::size::get_folder_size(path2)
.eager_get_links(true)
.call()
.ok()?;
let r = size1.cmp(&size2);
debug!(
tid = ?std::thread::current().id(),
?param,
?path1,
?path2,
?r
);
Some(r)
};
if let Some(r) = compare() {
return CompareIDsParam::to_result(Some(r));
}
} else if is_folder1 {
return CompareIDsParam::to_result(Some(cmp::Ordering::Greater));
} else if is_folder2 {
return CompareIDsParam::to_result(Some(cmp::Ordering::Less));
}
}
real()
}