在 rust 中如何获取windows文件管理当前 tab 中选中的文件路径?

在 rust 中如何使用 windows crate 获取文件管理当前 tab 中选中的文件路径

image.png

通过 IShellWindows 只能拿到选中的文件,但是没办法区分是哪个 tab 中选中的

fn get_select_file_path(hwnd: HWND) -> Option<String> {
        // hwnd 是通过 WindowsAndMessaging::GetForegroundWindow(); 获取的当前活动窗口句柄
        unsafe {
            // 初始化 COM 库
            let com = CoInitializeEx(None, COINIT_DISABLE_OLE1DDE);
            if com.is_err() {
                return None;
            }

            let hr: Result<IShellWindows, windows::core::Error> =
                CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER);
            // let hr = CoCreateInstance(&ShellWindows, None, CLSCTX_INPROC_HANDLER);
            if hr.is_err() {
                println!("创建 IShellWindows 失败");
                CoUninitialize(); // 清理 COM
                return None; // 创建 IShellWindows 失败
            }
            let shell_windows = hr.unwrap();

            let mut target_path = None;
            let count = shell_windows.Count().unwrap_or_default();

            for i in 0..count {
                let variant = VARIANT::from(i);
                let window: IDispatch = shell_windows.Item(&variant).ok()?;
                let web_browser: IWebBrowser2 = window.cast().ok()?;
                // 检查窗口是否与当前活动窗口匹配
                let item_hwnd = web_browser.HWND().ok()?;
                if item_hwnd.0 != hwnd.0 as isize {
                    continue;
                }
                let a = web_browser.LocationURL().ok()?;
                println!("web_browser Path: {:?}", a);
                
                // 通过IWebBrowser2获取文件夹视图并获取选中的项目
                let document = web_browser.Document().ok()?;
                let folder_view: IShellFolderViewDual3 = document.cast().ok()?;
                let selected_items = folder_view.SelectedItems().ok()?;
                let count = selected_items.Count().ok()?;
                
                if count > 0 {
                    let item = selected_items.Item(&VARIANT::from(0)).ok()?;
                    let path = item.Path().ok()?;
                    target_path = Some(path.to_string());
                    break
                }
            }
            // 清理 COM
            CoUninitialize();
            target_path
        }
    }

上述代码只能获得所有选中的文件

阅读 889
avatarAI BotBETA

在 Rust 中,通过 windows crate 访问 Windows API 来获取特定文件管理器(如 Windows Explorer)中当前标签页中选中的文件路径,确实是一个复杂的过程,因为 Windows Explorer 的内部实现和接口可能随着 Windows 版本的更新而变化。你当前的代码尝试通过 IShellWindowsIWebBrowser2 接口来访问这些信息,但这种方法主要适用于旧的 Internet Explorer 组件集成,并且可能无法直接应用于现代的文件管理器实现。

对于现代 Windows 10 和 Windows 11,文件管理器(File Explorer)更多地依赖于 UWP(Universal Windows Platform)组件,这些组件的接口通常不是通过传统的 COM 接口直接暴露的。

不过,有几种可能的方法可以尝试:

  1. 使用 UI Automation(UIA)
    UI Automation 是一个用于访问和操作用户界面元素的框架,它支持多种应用程序,包括 Windows Explorer。你可以使用 Rust 的 uiautomation crate(如果可用)或调用 Windows 的 UIA API 来查询当前选中的文件路径。
  2. 使用 Windows Script Host(WSH)
    虽然这不是 Rust 的直接方法,但你可以通过 Rust 调用 PowerShell 脚本或使用其他支持 WSH 的技术来执行文件管理器操作。
  3. 模拟用户输入
    如果其他方法都不可行,你可以考虑模拟键盘和鼠标输入来导航到文件管理器中的特定位置并选择文件。这种方法非常不稳定且容易出错,通常不推荐使用。
  4. 使用第三方库
    检查是否有第三方 Rust 库或绑定,这些库可能已经封装了与文件管理器交互的功能。
  5. 直接读取注册表或系统文件
    在某些情况下,文件管理器的某些状态可能会存储在注册表或系统文件中。然而,这种方法非常依赖于 Windows 的内部实现,并且可能随着 Windows 版本的更新而失效。

对于你的具体需求,使用 UI Automation 可能是最可行的方法。不过,请注意,UI Automation 的实现可能相当复杂,并且需要深入了解 Windows 用户界面的结构和行为。

如果你决定使用 UI Automation,你可能需要查找特定于文件管理器中文件列表和标签页的控件模式(如 ListTreeTab 等),并编写代码来遍历这些控件以找到当前选中的文件。这通常涉及到监听控件的事件、查询控件的属性以及模拟用户交互等。

由于这是一个高级主题,并且超出了简单回答的范围,我建议你深入研究 UI Automation 的文档和示例,以了解如何将其应用于你的特定需求。

1 个回答

由于是在 tauri 中使用的,在tauri 中需要单独开一个线程才能处理 COM 接口相关的。

unsafe fn get_selected_file_from_explorer() -> Result<String, WError> {
        let (tx, rx) = mpsc::channel();

        // 在新的线程中执行 COM 操作
        thread::spawn(move || {
            let result: Result<String, WError> = (|| -> Result<String, WError> {
                // 在子线程中初始化 COM 库为单线程单元
                let _ = CoInitializeEx(None, COINIT_APARTMENTTHREADED);

                let hwnd_gfw = WindowsAndMessaging::GetForegroundWindow();
                let shell_windows: IShellWindows =
                    CoCreateInstance(&ShellWindows, None, CLSCTX_LOCAL_SERVER)?;
                let result_hwnd =
                    WindowsAndMessaging::FindWindowExW(hwnd_gfw, None, w!("ShellTabWindowClass"), None)?;

                let mut target_path = String::new();
                let count = shell_windows.Count().unwrap_or_default();

                for i in 0..count {
                    let variant = VARIANT::from(i);
                    let dispatch: IDispatch = shell_windows.Item(&variant)?;

                    let shell_browser = Self::dispath2browser(dispatch);

                    if shell_browser.is_none() {
                        continue;
                    }
                    let shell_browser = shell_browser.unwrap();
                    // 调用 GetWindow 可能会阻塞 GUI 消息
                    let phwnd = shell_browser.GetWindow()?;
                    if hwnd_gfw.0 != phwnd.0 && result_hwnd.0 != phwnd.0 {
                        continue;
                    }

                    let shell_view = shell_browser.QueryActiveShellView().unwrap();
                    target_path = Self::get_selected_file_path_from_shellview(shell_view);
                }

                Ok(target_path)
        
            })();
            tx.send(result).unwrap();
            
        });
        let target_path = rx.recv().unwrap()?;

        Ok(target_path)
    }
unsafe fn dispath2browser(dispatch: IDispatch) -> Option<IShellBrowser> {
        
        let mut service_provider: Option<IServiceProvider> = None;
        dispatch
            .query(
                &IServiceProvider::IID,
                &mut service_provider as *mut _ as *mut _,
            )
            .ok()
            .unwrap();
        if service_provider.is_none() {
            return None;
        }
        let shell_browser = service_provider
            .unwrap()
            .QueryService::<IShellBrowser>(&IShellBrowser::IID)
            .ok();
        shell_browser
    }

    unsafe fn get_selected_file_path_from_shellview(shell_view: IShellView) -> String {
        let mut target_path = String::new();
        let shell_items = shell_view.GetItemObject::<IShellItemArray>(SVGIO_SELECTION);

        if shell_items.is_err() {
            return target_path;
        }
        println!("shell_items: {:?}", shell_items);
        let shell_items = shell_items.unwrap();
        let count = shell_items.GetCount().unwrap_or_default();
        for i in 0..count {
            let shell_item = shell_items.GetItemAt(i).unwrap();

            // 如果不是文件对象则继续循环
            if let Ok(attrs) = shell_item.GetAttributes(SFGAO_FILESYSTEM) {
                log::info!("attrs: {:?}", attrs);
                if attrs.0 == 0 {
                    continue;
                }
            }

            if let Ok(display_name) = shell_item.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING)
            {
                let tmp = display_name.to_string();
                if tmp.is_err() {
                    continue;
                }
                target_path = tmp.unwrap();
                break;
            }

            if let Ok(display_name) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
                println!("display_name: {:?}", display_name);
                let tmp = display_name.to_string();
                if tmp.is_err() {
                    println!("display_name error: {:?}", tmp.err());
                    continue;
                }
                target_path = tmp.unwrap();
                break;
            }
            
        }
        target_path
    }
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进