Impl prompts and savefile dialog on Windows (#9009)

### Description
This is a part of #8809 , and this PR dose not include `open file
dialog`, as I already saw two PRs impl this.



https://github.com/zed-industries/zed/assets/14981363/3223490a-de77-4892-986f-97cf85aec3ae




Release Notes:

- N/A
This commit is contained in:
张小白 2024-03-09 00:14:47 +08:00 committed by GitHub
parent bf295eac90
commit a550b9cecf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 147 additions and 13 deletions

View File

@ -329,9 +329,11 @@ features = [
"Wdk_System_SystemServices", "Wdk_System_SystemServices",
"Win32_Graphics_Gdi", "Win32_Graphics_Gdi",
"Win32_Graphics_DirectComposition", "Win32_Graphics_DirectComposition",
"Win32_UI_Controls",
"Win32_UI_WindowsAndMessaging", "Win32_UI_WindowsAndMessaging",
"Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_Shell", "Win32_UI_Shell",
"Win32_System_Com",
"Win32_System_SystemInformation", "Win32_System_SystemInformation",
"Win32_System_SystemServices", "Win32_System_SystemServices",
"Win32_System_Time", "Win32_System_Time",

View File

@ -109,9 +109,13 @@ copypasta = "0.10.1"
open = "5.0.1" open = "5.0.1"
ashpd = "0.7.0" ashpd = "0.7.0"
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] }
wayland-client= { version = "0.31.2" } wayland-client = { version = "0.31.2" }
wayland-cursor = "0.31.1" wayland-cursor = "0.31.1"
wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] } wayland-protocols = { version = "0.31.2", features = [
"client",
"staging",
"unstable",
] }
wayland-backend = { version = "0.3.3", features = ["client_system"] } wayland-backend = { version = "0.3.3", features = ["client_system"] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] } xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
as-raw-xcb-connection = "1" as-raw-xcb-connection = "1"

View File

@ -5,6 +5,7 @@ use std::{
cell::RefCell, cell::RefCell,
collections::HashSet, collections::HashSet,
ffi::{c_uint, c_void}, ffi::{c_uint, c_void},
os::windows::ffi::OsStrExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::Rc, rc::Rc,
sync::Arc, sync::Arc,
@ -14,7 +15,8 @@ use std::{
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use async_task::Runnable; use async_task::Runnable;
use copypasta::{ClipboardContext, ClipboardProvider}; use copypasta::{ClipboardContext, ClipboardProvider};
use futures::channel::oneshot::Receiver; use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use parking_lot::Mutex; use parking_lot::Mutex;
use time::UtcOffset; use time::UtcOffset;
use util::{ResultExt, SemanticVersion}; use util::{ResultExt, SemanticVersion};
@ -25,15 +27,17 @@ use windows::{
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
Graphics::DirectComposition::DCompositionWaitForCompositorClock, Graphics::DirectComposition::DCompositionWaitForCompositorClock,
System::{ System::{
Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL},
Ole::{OleInitialize, OleUninitialize},
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID}, Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
{
Ole::{OleInitialize, OleUninitialize},
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
},
}, },
UI::{ UI::{
Input::KeyboardAndMouse::GetDoubleClickTime, Input::KeyboardAndMouse::GetDoubleClickTime,
Shell::ShellExecuteW, Shell::{
FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName,
ShellExecuteW, SIGDN_FILESYSPATH,
},
WindowsAndMessaging::{ WindowsAndMessaging::{
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS,
@ -342,9 +346,32 @@ impl Platform for WindowsPlatform {
unimplemented!() unimplemented!()
} }
// todo(windows)
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> { fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> {
unimplemented!() let directory = directory.to_owned();
let (tx, rx) = oneshot::channel();
self.foreground_executor()
.spawn(async move {
unsafe {
let Ok(dialog) = show_savefile_dialog(directory) else {
let _ = tx.send(None);
return;
};
let Ok(_) = dialog.Show(None) else {
let _ = tx.send(None); // user cancel
return;
};
if let Ok(shell_item) = dialog.GetResult() {
if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap())));
return;
}
}
let _ = tx.send(None);
}
})
.detach();
rx
} }
fn reveal_path(&self, path: &Path) { fn reveal_path(&self, path: &Path) {
@ -555,3 +582,26 @@ fn open_target(target: &str) {
} }
} }
} }
unsafe fn show_savefile_dialog(directory: PathBuf) -> Result<IFileSaveDialog> {
let dialog: IFileSaveDialog = CoCreateInstance(&FileSaveDialog, None, CLSCTX_ALL)?;
let bind_context = CreateBindCtx(0)?;
let Ok(full_path) = directory.canonicalize() else {
return Ok(dialog);
};
let dir_str = full_path.into_os_string();
if dir_str.is_empty() {
return Ok(dialog);
}
let dir_vec = dir_str.encode_wide().collect_vec();
let ret = SHCreateItemFromParsingName(PCWSTR::from_raw(dir_vec.as_ptr()), &bind_context)
.inspect_err(|e| log::error!("unable to create IShellItem: {}", e));
if ret.is_ok() {
let dir_shell_item: IShellItem = ret.unwrap();
let _ = dialog
.SetFolder(&dir_shell_item)
.inspect_err(|e| log::error!("unable to set folder for save file dialog: {}", e));
}
Ok(dialog)
}

View File

@ -6,6 +6,7 @@ use std::{
any::Any, any::Any,
cell::{Cell, RefCell}, cell::{Cell, RefCell},
ffi::c_void, ffi::c_void,
iter::once,
num::NonZeroIsize, num::NonZeroIsize,
path::PathBuf, path::PathBuf,
rc::{Rc, Weak}, rc::{Rc, Weak},
@ -14,7 +15,8 @@ use std::{
}; };
use blade_graphics as gpu; use blade_graphics as gpu;
use futures::channel::oneshot::Receiver; use futures::channel::oneshot::{self, Receiver};
use itertools::Itertools;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use smallvec::SmallVec; use smallvec::SmallVec;
use windows::{ use windows::{
@ -33,6 +35,10 @@ use windows::{
}, },
}, },
UI::{ UI::{
Controls::{
TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON,
TD_INFORMATION_ICON, TD_WARNING_ICON,
},
Input::KeyboardAndMouse::{ Input::KeyboardAndMouse::{
GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1,
VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR,
@ -778,7 +784,6 @@ impl PlatformWindow for WindowsWindow {
self.inner.input_handler.take() self.inner.input_handler.take()
} }
// todo(windows)
fn prompt( fn prompt(
&self, &self,
level: PromptLevel, level: PromptLevel,
@ -786,7 +791,72 @@ impl PlatformWindow for WindowsWindow {
detail: Option<&str>, detail: Option<&str>,
answers: &[&str], answers: &[&str],
) -> Option<Receiver<usize>> { ) -> Option<Receiver<usize>> {
unimplemented!() let (done_tx, done_rx) = oneshot::channel();
let msg = msg.to_string();
let detail_string = match detail {
Some(info) => Some(info.to_string()),
None => None,
};
let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
let handle = self.inner.hwnd;
self.inner
.platform_inner
.foreground_executor
.spawn(async move {
unsafe {
let mut config;
config = std::mem::zeroed::<TASKDIALOGCONFIG>();
config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
config.hwndParent = handle;
let title;
let main_icon;
match level {
crate::PromptLevel::Info => {
title = windows::core::w!("Info");
main_icon = TD_INFORMATION_ICON;
}
crate::PromptLevel::Warning => {
title = windows::core::w!("Warning");
main_icon = TD_WARNING_ICON;
}
crate::PromptLevel::Critical => {
title = windows::core::w!("Critical");
main_icon = TD_ERROR_ICON;
}
};
config.pszWindowTitle = title;
config.Anonymous1.pszMainIcon = main_icon;
let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
let hints_encoded;
if let Some(ref hints) = detail_string {
hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
};
let mut buttons = Vec::new();
let mut btn_encoded = Vec::new();
for (index, btn_string) in answers.iter().enumerate() {
let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
buttons.push(TASKDIALOG_BUTTON {
nButtonID: index as _,
pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
});
btn_encoded.push(encoded);
}
config.cButtons = buttons.len() as _;
config.pButtons = buttons.as_ptr();
config.pfCallback = None;
let mut res = std::mem::zeroed();
let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
.inspect_err(|e| log::error!("unable to create task dialog: {}", e));
let _ = done_tx.send(res as usize);
}
})
.detach();
Some(done_rx)
} }
// todo(windows) // todo(windows)

View File

@ -5,4 +5,12 @@
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
</asmv3:windowsSettings> </asmv3:windowsSettings>
</asmv3:application> </asmv3:application>
<dependency>
<dependentAssembly>
<assemblyIdentity type='win32'
name='Microsoft.Windows.Common-Controls'
version='6.0.0.0' processorArchitecture='*'
publicKeyToken='6595b64144ccf1df' />
</dependentAssembly>
</dependency>
</assembly> </assembly>