ib_shell_item/hook/folder/mod.rs
1use std::{cell::SyncUnsafeCell, cmp, ffi::c_void, path::Path};
2
3use bon::Builder;
4use ib_hook::inline::InlineHook;
5use serde::{Deserialize, Serialize};
6use tracing::debug;
7use windows::{
8 Win32::{
9 Foundation::LPARAM,
10 UI::Shell::{Common::ITEMIDLIST, IShellFolder},
11 },
12 core::{HRESULT, Interface},
13};
14
15use crate::{
16 folder::{CompareIDs as CompareIDsParam, ShellFolder},
17 id_list::ChildIDRef,
18 prop::column,
19};
20
21#[derive(Default, Serialize, Deserialize, Clone, Builder, Debug)]
22pub struct FolderHookConfig {
23 /// This also reverses the order of folder vs. file,
24 /// i.e. from file > folder to folder > file.
25 /// (降序排序时,文件夹优先于文件)
26 ///
27 /// TODO: Only if `_IsReal()`?
28 #[cfg(feature = "everything")]
29 #[builder(default)]
30 compare_size_from_everything: bool,
31}
32
33type CompareIDs =
34 unsafe extern "system" fn(*mut c_void, LPARAM, *const ITEMIDLIST, *const ITEMIDLIST) -> HRESULT;
35
36pub(crate) struct FolderHook {
37 compare_ids: Option<InlineHook<CompareIDs>>,
38}
39
40impl FolderHook {
41 pub fn new(config: FolderHookConfig) -> anyhow::Result<Self> {
42 let folder = IShellFolder::from_fs_any(Default::default())?;
43
44 let compare_ids = if config.compare_size_from_everything {
45 let compare_ids = folder.vtable().CompareIDs;
46 InlineHook::new_enabled(compare_ids, compare_ids_detour).ok()
47 } else {
48 None
49 };
50
51 Ok(Self { compare_ids })
52 }
53}
54
55pub(crate) static HOOK: SyncUnsafeCell<Option<FolderHook>> = SyncUnsafeCell::new(None);
56
57pub fn apply(config: Option<FolderHookConfig>) -> anyhow::Result<()> {
58 let hook = unsafe { &mut *HOOK.get() };
59 *hook = match config {
60 Some(config) => Some(FolderHook::new(config)?),
61 None => None,
62 };
63 Ok(())
64}
65
66unsafe extern "system" fn compare_ids_detour(
67 this: *mut c_void,
68 lparam: LPARAM,
69 pidl1: *const ITEMIDLIST,
70 pidl2: *const ITEMIDLIST,
71) -> HRESULT {
72 let hook = unsafe { &*HOOK.get() }.as_ref().unwrap();
73 let real =
74 || unsafe { hook.compare_ids.as_ref().unwrap().trampoline()(this, lparam, pidl1, pidl2) };
75
76 let param = CompareIDsParam::from(lparam);
77 /*
78 debug!(?this, ?param, ?pidl1, ?pidl2);
79 debug!(folder = ?{
80 let folder = unsafe { IShellFolder::from_raw_borrowed(&this) }.unwrap();
81 let pidl1 = unsafe { ChildIDRef::from_raw(pidl1) };
82 let pidl2 = unsafe { ChildIDRef::from_raw(pidl2) };
83 let path1 = folder.get_path_of(pidl1).ok();
84 let path2 = folder.get_path_of(pidl2).ok();
85 (
86 path1,
87 path2,
88 )
89 });
90 debug!(
91 ?param,
92 folder = ?{
93 let folder = unsafe { IShellFolder::from_raw_borrowed(&this) }.unwrap();
94 let pidl1 = unsafe { ChildIDRef::from_raw(pidl1) };
95 let pidl2 = unsafe { ChildIDRef::from_raw(pidl2) };
96 let path1 = folder.get_path_of(pidl1).ok();
97 let path2 = folder.get_path_of(pidl2).ok();
98 (
99 folder.is_child_folder(pidl1),
100 folder.is_child_folder(pidl2),
101 path1,
102 path2,
103 )
104 }
105 );
106 */
107
108 /*
109 static LAST_PARAM: SyncUnsafeCell<u16> = SyncUnsafeCell::new(0);
110 static LAST_PIDL1: SyncUnsafeCell<usize> = SyncUnsafeCell::new(0);
111 static LAST_PIDL2: SyncUnsafeCell<usize> = SyncUnsafeCell::new(0);
112 */
113
114 // 怎么知道现在排序的列和顺序?
115 // 列用时间勉强可以
116 #[cfg(feature = "everything")]
117 if param.column == column::FSColumn::Size as u16
118 /*
119 || (param.column == column::FSColumn::ItemNameDisplay as u16
120 && unsafe { *LAST_PARAM.get() } == column::FSColumn::Size as u16
121 && unsafe { *LAST_PIDL1.get() } == pidl1 as usize
122 && unsafe { *LAST_PIDL2.get() } == pidl2 as usize)
123 */
124 {
125 /*
126 debug!(?this, ?param, ?pidl1, ?pidl2, "Size");
127 */
128 let folder = unsafe { IShellFolder::from_raw_borrowed(&this) }.unwrap();
129 let pidl1 = unsafe { ChildIDRef::from_raw(pidl1) };
130 let pidl2 = unsafe { ChildIDRef::from_raw(pidl2) };
131
132 // is_child_has_subfolder() may cause serious lag
133 let is_folder1 = folder.is_child_fs_folder(pidl1);
134 let is_folder2 = folder.is_child_fs_folder(pidl2);
135
136 /*
137 debug!(?param, path1 = ?folder.get_path_of(pidl1).ok(), path2 = ?folder.get_path_of(pidl2).ok(), is_folder1, is_folder2, "Size");
138 */
139 if is_folder1 && is_folder2 {
140 let compare = || {
141 let path1 = folder.get_path_of(pidl1).ok()?.to_string();
142 let path2 = folder.get_path_of(pidl2).ok()?.to_string();
143 #[cfg(debug_assertions)]
144 if path1 == path2 {
145 // Yes, this may happen, and pidl1 != pidl2, on at least Windows 11 24H2
146 debug!("same path");
147 return Some(cmp::Ordering::Equal);
148 }
149 let path1 = Path::new(&path1);
150 let path2 = Path::new(&path2);
151
152 let size1 = everything_ipc::folder::size::get_folder_size(path1)
153 .eager_get_links(true)
154 .call()
155 .ok()?;
156 /*
157 if size1 == 0 {
158 return Some(cmp::Ordering::Less);
159 }
160 */
161 let size2 = everything_ipc::folder::size::get_folder_size(path2)
162 .eager_get_links(true)
163 .call()
164 .ok()?;
165
166 let r = size1.cmp(&size2);
167 debug!(
168 tid = ?std::thread::current().id(),
169 ?param,
170 ?path1,
171 ?path2,
172 ?r
173 );
174 Some(r)
175 };
176 if let Some(r) = compare() {
177 /*
178 if (r as i32) < 0 {
179 unsafe { *LAST_PARAM.get() = param.column };
180 unsafe { *LAST_PIDL1.get() = pidl1.0 as usize };
181 unsafe { *LAST_PIDL2.get() = pidl2.0 as usize };
182 }
183 */
184 return CompareIDsParam::to_result(Some(r));
185 }
186 } else if is_folder1 {
187 // folder > file
188 return CompareIDsParam::to_result(Some(cmp::Ordering::Greater));
189 } else if is_folder2 {
190 // file < folder
191 return CompareIDsParam::to_result(Some(cmp::Ordering::Less));
192 }
193 }
194
195 real()
196}