优化调整

This commit is contained in:
zhongluofeng
2026-08-14 17:47:06 +08:00
parent 6c7897bf47
commit 7d49a7395f
50 changed files with 2805 additions and 259 deletions
+139
View File
@@ -0,0 +1,139 @@
//! 快速面板:检测前台 Explorer 窗口的当前目录。
//!
//! 必须在快捷键回调(`show_popup`)内调用:此时前台窗口仍是资源管理器,
//! 面板尚未取得焦点,`GetForegroundWindow` 拿到的才是 Explorer 主窗口;
//! 若等面板显示后再调用,前台就变成面板自身了。
//!
//! 思路(Listary / PowerToys Run 同款):前台窗口 HWND 匹配 `IShellWindows`
//! 中某个 Shell 窗口 → 取其 `LocationURL`file:///...)→ 转成本地路径。
//!
//! Win11 多选项卡:同一顶层窗口下每个选项卡都是独立的 `IShellWindows` 条目,
//! 共享顶层 HWND。活动选项卡的内容窗口(`ShellTabWindowClass`)在子窗口
//! z-order 顶层,用 `IID_IShellBrowser` 作为 `QueryService` 的 service ID 获取
//! 每个选项卡自己的 `IShellBrowser`(而非 `SID_STopLevelBrowser` 返回的顶层
//! browser),再通过 `GetWindow()` 拿到该选项卡的内容窗口句柄,与活动选项卡
//! 的内容窗口比对,从而定位当前正在浏览的选项卡(`IsWindowVisible` 对所有
//! 选项卡都成立,不可用)。
use std::path::Path;
#[cfg(windows)]
use windows::core::ComInterface;
#[cfg(windows)]
use windows::Win32::Foundation::HWND;
#[cfg(windows)]
use windows::Win32::System::Com::{
CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED,
IServiceProvider,
};
#[cfg(windows)]
use windows::Win32::System::Variant::{VARIANT, VT_I4};
#[cfg(windows)]
use windows::Win32::UI::Shell::{
IWebBrowserApp, IShellBrowser, IShellWindows, ShellWindows,
};
#[cfg(windows)]
use windows::Win32::UI::WindowsAndMessaging::{
GetClassNameW, GetForegroundWindow, GetWindow, GW_CHILD, GW_HWNDNEXT,
};
/// 检测前台 Explorer 窗口的当前目录。
/// 返回 `None`:前台不是 Explorer / COM 初始化失败 / URL 无法转路径。
#[cfg(windows)]
pub fn detect_explorer_folder() -> Option<String> {
// 首次 COM 初始化失败(例如已在 MTA 线程)时,后续 COM 调用一般仍可用,忽略错误继续。
let _ = unsafe { CoInitializeEx(None, COINIT_APARTMENTTHREADED) };
let fg = unsafe { GetForegroundWindow() };
let result = if fg.0 == 0 {
None
} else {
unsafe { find_folder_for_hwnd(fg) }
};
unsafe { CoUninitialize() };
result
}
#[cfg(windows)]
unsafe fn find_folder_for_hwnd(fg: HWND) -> Option<String> {
let shell: IShellWindows = CoCreateInstance(&ShellWindows, None, CLSCTX_ALL).ok()?;
let count = shell.Count().ok()?;
// Win11 多选项卡:活动选项卡的内容窗口(ShellTabWindowClass)在子窗口 z-order
// 顶层。在 IShellWindows 条目中,用 IShellBrowser::GetWindow() 取到的内容窗口
// HWND 与它比对,即可定位当前正在浏览的选项卡(IsWindowVisible 对所有选项卡
// 都成立,不可用)。
let active_tab = find_active_shell_tab(fg);
for i in 0..count {
// 索引过期的窗口会返回失败,跳过继续即可,不能 `?` 提前结束整个循环。
// 0.52 的 Win32 VARIANT 无 From<i32>,手动构造 VT_I4 变体。
let mut index = VARIANT::default();
{
let value = &mut *index.Anonymous.Anonymous;
value.vt = VT_I4;
value.Anonymous.lVal = i;
}
let Ok(dispatch) = shell.Item(index) else { continue };
let Ok(app) = dispatch.cast::<IWebBrowserApp>() else { continue };
// 只考虑前台顶层窗口对应的条目;同一窗口的多个选项卡条目共享顶层句柄。
let Ok(hwnd) = app.HWND() else { continue };
if HWND(hwnd.0) != fg {
continue;
}
// 有选项卡时,必须匹配活动选项卡的内容窗口;否则退化为任意条目(旧版无选项卡)。
if let Some(active) = active_tab {
let Ok(svc) = app.cast::<IServiceProvider>() else { continue };
let Ok(browser) = svc.QueryService::<IShellBrowser>(&IShellBrowser::IID) else {
continue;
};
let Ok(this_tab) = browser.GetWindow() else { continue };
if this_tab != active {
continue;
}
}
if let Ok(url) = app.LocationURL() {
return url_to_path(&url.to_string());
}
}
None
}
/// 枚举前台窗口的子窗口(z-order 自上而下),返回第一个类名为 `ShellTabWindowClass`
/// 的窗口句柄,即 Win11 资源管理器活动选项卡的内容窗口;无选项卡时返回 `None`。
#[cfg(windows)]
unsafe fn find_active_shell_tab(fg: HWND) -> Option<HWND> {
const CLASS: &str = "ShellTabWindowClass";
let mut hwnd = GetWindow(fg, GW_CHILD);
while hwnd.0 != 0 {
let mut buf = [0u16; 64];
let len = GetClassNameW(hwnd, &mut buf);
if len > 0 {
let name = String::from_utf16_lossy(&buf[..len as usize]);
if name == CLASS {
return Some(hwnd);
}
}
hwnd = GetWindow(hwnd, GW_HWNDNEXT);
}
None
}
/// 把 `file:///C:/xxx`(可能带百分号编码)转成本地路径,仅接受目录。
#[cfg(windows)]
fn url_to_path(url: &str) -> Option<String> {
if !url.starts_with("file:") {
return None;
}
let parsed = url::Url::parse(url).ok()?;
let path = parsed.to_file_path().ok()?;
let path = Path::new(&path);
if path.is_dir() {
Some(path.to_string_lossy().to_string())
} else {
None
}
}
/// 非 Windows 平台占位:保持模块可编译。
#[cfg(not(windows))]
pub fn detect_explorer_folder() -> Option<String> {
None
}