From c7e13c5bd8a0554d32b7c5fbf4b1d3df57cd646d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 25 Dec 2025 17:41:39 +0000 Subject: [PATCH] Fix: Improve Windows file opening and app listing Co-authored-by: ijamespine --- .../crates/file-opening-windows/src/lib.rs | 102 ++++++++++-------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/apps/tauri/crates/file-opening-windows/src/lib.rs b/apps/tauri/crates/file-opening-windows/src/lib.rs index 48fe6a774..f3bb47ee6 100644 --- a/apps/tauri/crates/file-opening-windows/src/lib.rs +++ b/apps/tauri/crates/file-opening-windows/src/lib.rs @@ -1,9 +1,10 @@ use file_opening::{FileOpener, OpenResult, OpenWithApp}; -use std::path::{Path, PathBuf}; -use windows::{ - core::*, Win32::Foundation::*, Win32::System::Com::*, Win32::UI::Shell::*, - Win32::UI::WindowsAndMessaging::*, -}; +use std::path::Path; +use windows::core::*; +use windows::Win32::Foundation::*; +use windows::Win32::System::Com::*; +use windows::Win32::UI::Shell::*; +use windows::Win32::UI::WindowsAndMessaging::*; // Thread-local COM initialization thread_local! { @@ -24,7 +25,7 @@ fn ensure_com_initialized() { pub struct WindowsFileOpener; impl FileOpener for WindowsFileOpener { - fn get_apps_for_file(&self, path: &Path) -> Result, String> { + fn get_apps_for_file(&self, path: &Path) -> std::result::Result, String> { ensure_com_initialized(); let ext = path @@ -40,7 +41,7 @@ impl FileOpener for WindowsFileOpener { list_apps_for_extension(&ext) } - fn open_with_default(&self, path: &Path) -> Result { + fn open_with_default(&self, path: &Path) -> std::result::Result { ensure_com_initialized(); let path_str = path.to_string_lossy(); @@ -49,17 +50,17 @@ impl FileOpener for WindowsFileOpener { unsafe { let result = ShellExecuteW(None, w!("open"), &h_path, None, None, SW_SHOWNORMAL); - if result.0 as i32 > 32 { + if result.0 as isize > 32 { Ok(OpenResult::Success) } else { Ok(OpenResult::PlatformError { - message: format!("ShellExecute failed with code {}", result.0), + message: format!("ShellExecute failed with code {}", result.0 as isize), }) } } } - fn open_with_app(&self, path: &Path, app_id: &str) -> Result { + fn open_with_app(&self, path: &Path, app_id: &str) -> std::result::Result { ensure_com_initialized(); let ext = path @@ -80,29 +81,37 @@ impl FileOpener for WindowsFileOpener { SHAssocEnumHandlers(&HSTRING::from(ext.as_str()), ASSOC_FILTER_RECOMMENDED) .map_err(|e| e.to_string())?; - for handler in handlers { - let handler = handler.map_err(|e| e.to_string())?; - let name = handler - .GetName() - .map_err(|e| e.to_string())? - .to_string() - .map_err(|e| e.to_string())?; + loop { + let mut handler_array: [Option; 1] = [None]; + let mut fetched = 0u32; + + if handlers.Next(&mut handler_array, Some(&mut fetched)).is_err() || fetched == 0 { + break; + } - if name == app_id { - // Create shell item from file path - let path_str = path.to_string_lossy(); - let h_path = HSTRING::from(&*path_str); - - let shell_item: IShellItem = - SHCreateItemFromParsingName(&h_path, None).map_err(|e| e.to_string())?; - - let data_object: IDataObject = shell_item - .BindToHandler(None, &BHID_DataObject) + if let Some(handler) = &handler_array[0] { + let name = handler + .GetName() + .map_err(|e| e.to_string())? + .to_string() .map_err(|e| e.to_string())?; - handler.Invoke(&data_object).map_err(|e| e.to_string())?; + if name == app_id { + // Create shell item from file path + let path_str = path.to_string_lossy(); + let h_path = HSTRING::from(&*path_str); - return Ok(OpenResult::Success); + let shell_item: IShellItem = + SHCreateItemFromParsingName(&h_path, None).map_err(|e| e.to_string())?; + + let data_object: IDataObject = shell_item + .BindToHandler(None, &BHID_DataObject) + .map_err(|e| e.to_string())?; + + handler.Invoke(&data_object).map_err(|e| e.to_string())?; + + return Ok(OpenResult::Success); + } } } @@ -113,25 +122,34 @@ impl FileOpener for WindowsFileOpener { } } -fn list_apps_for_extension(ext: &str) -> Result, String> { +fn list_apps_for_extension(ext: &str) -> std::result::Result, String> { unsafe { let handlers = SHAssocEnumHandlers(&HSTRING::from(ext), ASSOC_FILTER_RECOMMENDED) .map_err(|e| e.to_string())?; let mut apps = Vec::new(); - for handler in handlers { - let handler = handler.map_err(|e| e.to_string())?; - let name = handler - .GetName() - .map_err(|e| e.to_string())? - .to_string() - .map_err(|e| e.to_string())?; + + loop { + let mut handler_array: [Option; 1] = [None]; + let mut fetched = 0u32; + + if handlers.Next(&mut handler_array, Some(&mut fetched)).is_err() || fetched == 0 { + break; + } - apps.push(OpenWithApp { - id: name.clone(), - name, - icon: None, - }); + if let Some(handler) = &handler_array[0] { + let name = handler + .GetName() + .map_err(|e| e.to_string())? + .to_string() + .map_err(|e| e.to_string())?; + + apps.push(OpenWithApp { + id: name.clone(), + name, + icon: None, + }); + } } apps.sort_by(|a, b| a.name.cmp(&b.name));