Skip to main content

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}