Fix: Improve Windows file opening and app listing

Co-authored-by: ijamespine <ijamespine@me.com>
This commit is contained in:
Cursor Agent
2025-12-25 17:41:39 +00:00
parent 4d10dd5e14
commit c7e13c5bd8

View File

@@ -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<Vec<OpenWithApp>, String> {
fn get_apps_for_file(&self, path: &Path) -> std::result::Result<Vec<OpenWithApp>, 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<OpenResult, String> {
fn open_with_default(&self, path: &Path) -> std::result::Result<OpenResult, String> {
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<OpenResult, String> {
fn open_with_app(&self, path: &Path, app_id: &str) -> std::result::Result<OpenResult, String> {
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<IAssocHandler>; 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<Vec<OpenWithApp>, String> {
fn list_apps_for_extension(ext: &str) -> std::result::Result<Vec<OpenWithApp>, 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<IAssocHandler>; 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));