windows: Add file dialog using IFileOpenDialog (#8919)

Release Notes:

- Added a file dialog for Windows
This commit is contained in:
Jason Wen 2024-03-08 20:07:48 -08:00 committed by GitHub
parent d4ec78f328
commit 456efb53ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 77 additions and 9 deletions

3
.gitignore vendored
View File

@ -22,4 +22,5 @@ DerivedData/
**/*.db **/*.db
.pytest_cache .pytest_cache
.venv .venv
.blob_store .blob_store
.vscode

View File

@ -347,6 +347,7 @@ features = [
"Win32_System_Threading", "Win32_System_Threading",
"Win32_System_DataExchange", "Win32_System_DataExchange",
"Win32_System_Ole", "Win32_System_Ole",
"Win32_System_Com",
] ]
[patch.crates-io] [patch.crates-io]

View File

@ -2,10 +2,10 @@
#![allow(unused_variables)] #![allow(unused_variables)]
use std::{ use std::{
cell::RefCell, cell::{Cell, RefCell},
collections::HashSet, collections::HashSet,
ffi::{c_uint, c_void}, ffi::{c_uint, c_void, OsString},
os::windows::ffi::OsStrExt, os::windows::ffi::{OsStrExt, OsStringExt},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
@ -21,7 +21,7 @@ use parking_lot::Mutex;
use time::UtcOffset; use time::UtcOffset;
use util::{ResultExt, SemanticVersion}; use util::{ResultExt, SemanticVersion};
use windows::{ use windows::{
core::{HSTRING, PCWSTR}, core::{IUnknown, HRESULT, HSTRING, PCWSTR, PWSTR},
Wdk::System::SystemServices::RtlGetVersion, Wdk::System::SystemServices::RtlGetVersion,
Win32::{ Win32::{
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
@ -35,8 +35,9 @@ use windows::{
UI::{ UI::{
Input::KeyboardAndMouse::GetDoubleClickTime, Input::KeyboardAndMouse::GetDoubleClickTime,
Shell::{ Shell::{
FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName, FileOpenDialog, FileSaveDialog, IFileOpenDialog, IFileSaveDialog, IShellItem,
ShellExecuteW, SIGDN_FILESYSPATH, SHCreateItemFromParsingName, ShellExecuteW, FILEOPENDIALOGOPTIONS,
FOS_ALLOWMULTISELECT, FOS_FILEMUSTEXIST, FOS_PICKFOLDERS, SIGDN_FILESYSPATH,
}, },
WindowsAndMessaging::{ WindowsAndMessaging::{
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
@ -341,9 +342,74 @@ impl Platform for WindowsPlatform {
self.inner.callbacks.lock().open_urls = Some(callback); self.inner.callbacks.lock().open_urls = Some(callback);
} }
// todo(windows)
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> { fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
unimplemented!() let (tx, rx) = oneshot::channel();
self.foreground_executor()
.spawn(async move {
let tx = Cell::new(Some(tx));
// create file open dialog
let folder_dialog: IFileOpenDialog = unsafe {
CoCreateInstance::<std::option::Option<&IUnknown>, IFileOpenDialog>(
&FileOpenDialog,
None,
CLSCTX_ALL,
)
.unwrap()
};
// dialog options
let mut dialog_options: FILEOPENDIALOGOPTIONS = FOS_FILEMUSTEXIST;
if options.multiple {
dialog_options |= FOS_ALLOWMULTISELECT;
}
if options.directories {
dialog_options |= FOS_PICKFOLDERS;
}
unsafe {
folder_dialog.SetOptions(dialog_options).unwrap();
folder_dialog
.SetTitle(&HSTRING::from(OsString::from("Select a folder")))
.unwrap();
}
let hr = unsafe { folder_dialog.Show(None) };
if hr.is_err() {
if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
// user canceled error
if let Some(tx) = tx.take() {
tx.send(None).unwrap();
}
return;
}
}
let mut results = unsafe { folder_dialog.GetResults().unwrap() };
let mut paths: Vec<PathBuf> = Vec::new();
for i in 0..unsafe { results.GetCount().unwrap() } {
let mut item: IShellItem = unsafe { results.GetItemAt(i).unwrap() };
let mut path: PWSTR =
unsafe { item.GetDisplayName(SIGDN_FILESYSPATH).unwrap() };
let mut path_os_string = OsString::from_wide(unsafe { path.as_wide() });
paths.push(PathBuf::from(path_os_string));
}
if let Some(tx) = tx.take() {
if paths.len() == 0 {
tx.send(None).unwrap();
} else {
tx.send(Some(paths)).unwrap();
}
}
})
.detach();
rx
} }
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> { fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {