Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

windows: Add file dialog using IFileOpenDialog #8919

Merged
merged 10 commits into from
Mar 9, 2024
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ DerivedData/
.pytest_cache
.venv
.blob_store
.vscode
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,13 @@ features = [
"Win32_Graphics_Gdi",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell",
"Win32_System_SystemServices",
"Win32_Security",
"Win32_System_Threading",
"Win32_System_DataExchange",
"Win32_System_Ole",
"Win32_System_Com",
]

[patch.crates-io]
Expand Down
110 changes: 96 additions & 14 deletions crates/gpui/src/platform/windows/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
#![allow(unused_variables)]

use std::{
cell::RefCell,
cell::{Cell, RefCell},
collections::HashSet,
ffi::{c_uint, c_void},
ffi::{c_uint, c_void, OsString},
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
Expand All @@ -13,19 +13,27 @@ use std::{

use anyhow::{anyhow, Result};
use async_task::Runnable;
use futures::channel::oneshot::Receiver;
use futures::channel::oneshot::{self, Receiver};
use parking_lot::Mutex;
use time::UtcOffset;
use util::{ResultExt, SemanticVersion};
use windows::Win32::{
Foundation::{CloseHandle, GetLastError, HANDLE, HWND, WAIT_EVENT},
System::Threading::{CreateEventW, INFINITE},
UI::WindowsAndMessaging::{
DispatchMessageW, GetMessageW, MsgWaitForMultipleObjects, PostQuitMessage,
SystemParametersInfoW, TranslateMessage, MSG, QS_ALLINPUT, SPI_GETWHEELSCROLLCHARS,
SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
use windows::{core::{IUnknown, HRESULT, HSTRING}, Win32::{
Foundation::{CloseHandle, HANDLE, HWND, WAIT_EVENT},
System::{
Com::{CoCreateInstance, CoInitializeEx, CoUninitialize, CLSCTX_ALL, COINIT_APARTMENTTHREADED, COINIT_DISABLE_OLE1DDE},
Threading::{CreateEventW, INFINITE}
},
};
UI::{
Shell::{
FileOpenDialog, IFileOpenDialog, IShellItem, FILEOPENDIALOGOPTIONS, SIGDN
},
WindowsAndMessaging::{
DispatchMessageW, GetMessageW, MsgWaitForMultipleObjects, PostQuitMessage,
SystemParametersInfoW, TranslateMessage, MSG, QS_ALLINPUT, SPI_GETWHEELSCROLLCHARS,
SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
}
}
}};

use crate::{
try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
Expand Down Expand Up @@ -290,17 +298,91 @@ impl Platform for WindowsPlatform {

// todo!("windows")
fn open_url(&self, url: &str) {
// todo!("windows")
unimplemented!()
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved
}

// todo!("windows")
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.inner.callbacks.lock().open_urls = Some(callback);
}

// todo!("windows")
#[allow(overflowing_literals)]
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> {
unimplemented!()
println!("prompt_for_paths {:?}", options);
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved

let (tx, rx) = oneshot::channel();

self.foreground_executor().spawn(async move {
// initialize COM
let hr = unsafe { CoInitializeEx(Some(std::ptr::null_mut()), COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) };
if hr.is_err() { panic!("CoInitializeEx failed: {:?}", hr); }

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 (https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/ne-shobjidl_core-_fileopendialogoptions)
let mut dialog_options: u32 = 0x1000; // FOS_FILEMUSTEXIST

if options.multiple {
dialog_options |= 0x200; // FOS_ALLOWMULTISELECT
}

if options.directories {
dialog_options |= 0x20; // FOS_PICKFOLDERS
}

unsafe {
folder_dialog.SetOptions(FILEOPENDIALOGOPTIONS(dialog_options)).unwrap(); // FOS_PICKFOLDERS
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(0x800704C7) { // user canceled error (we don't want to crash)
if let Some(tx) = tx.take() {
tx.send(None).unwrap();
}
return;
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved
}
}

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 = unsafe { item.GetDisplayName(SIGDN(0x80058000)).unwrap() }; // SIGDN_FILESYSPATH
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved

paths.push(PathBuf::from(unsafe { path.to_string().unwrap() }));
the-jasoney marked this conversation as resolved.
Show resolved Hide resolved
}


if let Some(tx) = tx.take() {
if paths.len() == 0 {
tx.send(None).unwrap();
} else {
println!("paths: {:?}", paths);
tx.send(Some(paths)).unwrap();
}
}

// uninitialize COM
unsafe {
CoUninitialize();
}

}).detach();

rx
}

// todo!("windows")
Expand Down