From a5a86d10c93275d81d32bbe590b76d3431f894a8 Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Wed, 6 Mar 2024 20:25:48 +0800 Subject: [PATCH 1/5] task dialog --- Cargo.toml | 1 + crates/gpui/src/platform/windows/window.rs | 74 +++++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9c0f65e1a28eb..aa2e0b1f651e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -329,6 +329,7 @@ features = [ "Wdk_System_SystemServices", "Win32_Graphics_Gdi", "Win32_Graphics_DirectComposition", + "Win32_UI_Controls", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index a1eb86c137ad9..7e03eaf208d58 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -14,7 +14,8 @@ use std::{ }; 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 smallvec::SmallVec; use windows::{ @@ -33,6 +34,10 @@ use windows::{ }, }, UI::{ + Controls::{ + TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON, + TD_INFORMATION_ICON, TD_WARNING_ICON, + }, Input::KeyboardAndMouse::{ 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, @@ -786,7 +791,72 @@ impl PlatformWindow for WindowsWindow { detail: Option<&str>, answers: &[&str], ) -> Option> { - 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::>(); + let handle = self.inner.hwnd; + self.inner + .platform_inner + .foreground_executor + .spawn(async move { + unsafe { + let mut config; + config = std::mem::zeroed::(); + config.cbSize = std::mem::size_of::() 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().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().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().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(); + + done_rx } // todo(windows) From 4eb62edc29d83f994d09ff16869cfd7a9241673c Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Thu, 7 Mar 2024 20:50:29 +0800 Subject: [PATCH 2/5] link to Microsoft Common Controls 6.0 --- crates/gpui/Cargo.toml | 8 ++++++-- crates/gpui/src/platform/windows/window.rs | 5 +++-- crates/zed/resources/windows/manifest.xml | 8 ++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 26fe0b6501f22..b4b117fabb0e1 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -109,9 +109,13 @@ copypasta = "0.10.1" open = "5.0.1" ashpd = "0.7.0" 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-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"] } xkbcommon = { version = "0.7", features = ["wayland", "x11"] } as-raw-xcb-connection = "1" diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 7e03eaf208d58..89c284af65eb0 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -6,6 +6,7 @@ use std::{ any::Any, cell::{Cell, RefCell}, ffi::c_void, + iter::once, num::NonZeroIsize, path::PathBuf, rc::{Rc, Weak}, @@ -836,7 +837,7 @@ impl PlatformWindow for WindowsWindow { 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().collect_vec(); + 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()), @@ -856,7 +857,7 @@ impl PlatformWindow for WindowsWindow { }) .detach(); - done_rx + Some(done_rx) } // todo(windows) diff --git a/crates/zed/resources/windows/manifest.xml b/crates/zed/resources/windows/manifest.xml index a2276d35ca377..5490c54d07112 100644 --- a/crates/zed/resources/windows/manifest.xml +++ b/crates/zed/resources/windows/manifest.xml @@ -5,4 +5,12 @@ PerMonitorV2 + + + + + From 2f778b6b38e2b0abf50015a096a850e2e2878b0e Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Thu, 7 Mar 2024 21:47:55 +0800 Subject: [PATCH 3/5] fmt --- Cargo.toml | 1 + crates/gpui/src/platform/windows/platform.rs | 61 ++++++++++++++++++-- crates/gpui/src/platform/windows/window.rs | 4 +- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index aa2e0b1f651e0..da8a628ab4122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -333,6 +333,7 @@ features = [ "Win32_UI_WindowsAndMessaging", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", + "Win32_System_Com", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Time", diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 91dc956d406d8..bc67d16e2539c 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -5,6 +5,7 @@ use std::{ cell::RefCell, collections::HashSet, ffi::{c_uint, c_void}, + os::windows::ffi::OsStrExt, path::{Path, PathBuf}, rc::Rc, sync::Arc, @@ -14,7 +15,8 @@ use std::{ use anyhow::{anyhow, Result}; use async_task::Runnable; use copypasta::{ClipboardContext, ClipboardProvider}; -use futures::channel::oneshot::Receiver; +use futures::channel::oneshot::{self, Receiver}; +use itertools::Itertools; use parking_lot::Mutex; use time::UtcOffset; use util::{ResultExt, SemanticVersion}; @@ -25,6 +27,8 @@ use windows::{ Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, Graphics::DirectComposition::DCompositionWaitForCompositorClock, System::{ + Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL}, + Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID}, { Ole::{OleInitialize, OleUninitialize}, @@ -33,7 +37,10 @@ use windows::{ }, UI::{ Input::KeyboardAndMouse::GetDoubleClickTime, - Shell::ShellExecuteW, + Shell::{ + FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName, + ShellExecuteW, SIGDN_DESKTOPABSOLUTEPARSING, + }, WindowsAndMessaging::{ DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, @@ -342,9 +349,32 @@ impl Platform for WindowsPlatform { unimplemented!() } - // todo(windows) fn prompt_for_new_path(&self, directory: &Path) -> Receiver> { - 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_DESKTOPABSOLUTEPARSING) { + let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap()))); + return; + } + } + let _ = tx.send(None); + } + }) + .detach(); + + rx } fn reveal_path(&self, path: &Path) { @@ -555,3 +585,26 @@ fn open_target(target: &str) { } } } + +unsafe fn show_savefile_dialog(directory: PathBuf) -> Result { + 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) +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 89c284af65eb0..604d2487dfc33 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -827,11 +827,11 @@ impl PlatformWindow for WindowsWindow { }; config.pszWindowTitle = title; config.Anonymous1.pszMainIcon = main_icon; - let instruction = msg.encode_utf16().collect_vec(); + 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().collect_vec(); + hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec(); config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr()); }; let mut buttons = Vec::new(); From 5984d202809029a880ee7e03d4edefb8e099f8ae Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Thu, 7 Mar 2024 23:21:56 +0800 Subject: [PATCH 4/5] remove `todo` --- crates/gpui/src/platform/windows/window.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 604d2487dfc33..505e785d2c750 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -784,7 +784,6 @@ impl PlatformWindow for WindowsWindow { self.inner.input_handler.take() } - // todo(windows) fn prompt( &self, level: PromptLevel, From cf66649db7dcffb16763f3db2b30fb46053a3eec Mon Sep 17 00:00:00 2001 From: Junkui Zhang <364772080@qq.com> Date: Fri, 8 Mar 2024 13:54:13 +0800 Subject: [PATCH 5/5] run fmt --- crates/gpui/src/platform/windows/platform.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index bc67d16e2539c..3f00aefe1eb56 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -28,18 +28,15 @@ use windows::{ Graphics::DirectComposition::DCompositionWaitForCompositorClock, System::{ Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL}, + Ole::{OleInitialize, OleUninitialize}, Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID}, - { - Ole::{OleInitialize, OleUninitialize}, - Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, - }, }, UI::{ Input::KeyboardAndMouse::GetDoubleClickTime, Shell::{ FileSaveDialog, IFileSaveDialog, IShellItem, SHCreateItemFromParsingName, - ShellExecuteW, SIGDN_DESKTOPABSOLUTEPARSING, + ShellExecuteW, SIGDN_FILESYSPATH, }, WindowsAndMessaging::{ DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, @@ -364,7 +361,7 @@ impl Platform for WindowsPlatform { return; }; if let Ok(shell_item) = dialog.GetResult() { - if let Ok(file) = shell_item.GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING) { + if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) { let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap()))); return; }