From b50f86735fd4414096c2c8aa387ce9b787e88aa5 Mon Sep 17 00:00:00 2001 From: Small White <364772080@qq.com> Date: Fri, 8 Mar 2024 07:59:48 +0800 Subject: [PATCH] Impl drag-drop action for Windows (#8959) ### Description This is a part of #8809 https://github.com/zed-industries/zed/assets/14981363/2b085b9d-8b83-4ac7-8b84-07c679760eba Release Notes: - N/A --------- Co-authored-by: Mikayla Maki --- Cargo.lock | 24 +++ Cargo.toml | 3 + crates/gpui/src/platform/windows/platform.rs | 27 +++- crates/gpui/src/platform/windows/window.rs | 157 ++++++++++++++++++- typos.toml | 2 + 5 files changed, 202 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3cc2883670..d5f5377f05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12160,6 +12160,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" dependencies = [ "windows-core", + "windows-implement", + "windows-interface", "windows-targets 0.52.4", ] @@ -12173,6 +12175,28 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "windows-implement" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942ac266be9249c84ca862f0a164a39533dc2f6f33dc98ec89c8da99b82ea0bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "windows-interface" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da33557140a288fae4e1d5f8873aaf9eb6613a9cf82c3e070223ff177f598b60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "windows-result" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6215271a94..9c0f65e1a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -325,6 +325,7 @@ sys-locale = "0.3.1" [workspace.dependencies.windows] version = "0.53.0" features = [ + "implement", "Wdk_System_SystemServices", "Win32_Graphics_Gdi", "Win32_Graphics_DirectComposition", @@ -335,6 +336,8 @@ features = [ "Win32_System_SystemServices", "Win32_System_Time", "Win32_Security", + "Win32_System_Com", + "Win32_System_Com_StructuredStorage", "Win32_System_Threading", "Win32_System_DataExchange", "Win32_System_Ole", diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 5482b56e89..6ead99dd2c 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -24,19 +24,21 @@ use windows::{ Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE}, Graphics::DirectComposition::DCompositionWaitForCompositorClock, System::{ - Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID}, + { + Ole::{OleInitialize, OleUninitialize}, + Threading::{CreateEventW, GetCurrentThreadId, INFINITE}, + }, }, UI::{ Input::KeyboardAndMouse::GetDoubleClickTime, Shell::ShellExecuteW, WindowsAndMessaging::{ - DispatchMessageW, EnumThreadWindows, LoadImageW, MsgWaitForMultipleObjects, - PeekMessageW, PostQuitMessage, SetCursor, SystemParametersInfoW, TranslateMessage, - HCURSOR, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, - IMAGE_CURSOR, LR_DEFAULTSIZE, LR_SHARED, MSG, PM_REMOVE, QS_ALLINPUT, - SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SW_SHOWDEFAULT, - SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, + DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage, + SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, + IDC_HAND, IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, IMAGE_CURSOR, LR_DEFAULTSIZE, + LR_SHARED, MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, + SW_SHOWDEFAULT, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, }, }, }, @@ -147,6 +149,9 @@ impl WindowsPlatformSystemSettings { impl WindowsPlatform { pub(crate) fn new() -> Self { + unsafe { + OleInitialize(None).expect("unable to initialize Windows OLE"); + } let (main_sender, main_receiver) = flume::unbounded::(); let event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event)); @@ -518,6 +523,14 @@ impl Platform for WindowsPlatform { } } +impl Drop for WindowsPlatform { + fn drop(&mut self) { + unsafe { + OleUninitialize(); + } + } +} + unsafe fn load_cursor(name: PCWSTR) -> Result { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED).map_err(|e| anyhow!(e)) } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 053d64569d..a1eb86c137 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -7,20 +7,30 @@ use std::{ cell::{Cell, RefCell}, ffi::c_void, num::NonZeroIsize, + path::PathBuf, rc::{Rc, Weak}, + str::FromStr, sync::{Arc, Once}, }; use blade_graphics as gpu; use futures::channel::oneshot::Receiver; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use smallvec::SmallVec; use windows::{ - core::{w, HSTRING, PCWSTR}, + core::{implement, w, HSTRING, PCWSTR}, Win32::{ - Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}, + Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, MAX_PATH, POINTL, S_OK, WPARAM}, Graphics::Gdi::{BeginPaint, EndPaint, InvalidateRect, PAINTSTRUCT}, - System::SystemServices::{ - MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS, + System::{ + Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, + Ole::{ + IDropTarget, IDropTarget_Impl, RegisterDragDrop, ReleaseStgMedium, RevokeDragDrop, + CF_HDROP, DROPEFFECT, DROPEFFECT_LINK, DROPEFFECT_NONE, + }, + SystemServices::{ + MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS, + }, }, UI::{ Input::KeyboardAndMouse::{ @@ -28,6 +38,7 @@ use windows::{ VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_SPACE, VK_TAB, VK_UP, }, + Shell::{DragQueryFileW, HDROP}, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, @@ -559,6 +570,14 @@ impl WindowsWindowInner { } LRESULT(1) } + + fn handle_drag_drop(&self, input: PlatformInput) { + let mut callbacks = self.callbacks.borrow_mut(); + let Some(ref mut func) = callbacks.input else { + return; + }; + func(input); + } } #[derive(Default)] @@ -576,6 +595,7 @@ struct Callbacks { pub(crate) struct WindowsWindow { inner: Rc, + drag_drop_handler: IDropTarget, } struct WindowCreateContext { @@ -640,8 +660,19 @@ impl WindowsWindow { lpparam, ) }; + let drag_drop_handler = { + let inner = context.inner.as_ref().unwrap(); + let handler = WindowsDragDropHandler(Rc::clone(inner)); + let drag_drop_handler: IDropTarget = handler.into(); + unsafe { + RegisterDragDrop(inner.hwnd, &drag_drop_handler) + .expect("unable to register drag-drop event") + }; + drag_drop_handler + }; let wnd = Self { inner: context.inner.unwrap(), + drag_drop_handler, }; platform_inner.window_handles.borrow_mut().insert(handle); match options.bounds { @@ -679,6 +710,14 @@ impl HasDisplayHandle for WindowsWindow { } } +impl Drop for WindowsWindow { + fn drop(&mut self) { + unsafe { + let _ = RevokeDragDrop(self.inner.hwnd); + } + } +} + impl PlatformWindow for WindowsWindow { fn bounds(&self) -> WindowBounds { WindowBounds::Fixed(Bounds { @@ -836,6 +875,113 @@ impl PlatformWindow for WindowsWindow { } } +#[implement(IDropTarget)] +struct WindowsDragDropHandler(pub Rc); + +impl IDropTarget_Impl for WindowsDragDropHandler { + fn DragEnter( + &self, + pdataobj: Option<&IDataObject>, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + unsafe { + let Some(idata_obj) = pdataobj else { + log::info!("no dragging file or directory detected"); + return Ok(()); + }; + let config = FORMATETC { + cfFormat: CF_HDROP.0, + ptd: std::ptr::null_mut() as _, + dwAspect: DVASPECT_CONTENT.0, + lindex: -1, + tymed: TYMED_HGLOBAL.0 as _, + }; + let mut paths = SmallVec::<[PathBuf; 2]>::new(); + if idata_obj.QueryGetData(&config as _) == S_OK { + *pdweffect = DROPEFFECT_LINK; + let Ok(mut idata) = idata_obj.GetData(&config as _) else { + return Ok(()); + }; + if idata.u.hGlobal.is_invalid() { + return Ok(()); + } + let hdrop = idata.u.hGlobal.0 as *mut HDROP; + let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None); + for file_index in 0..file_count { + let mut buffer = [0u16; MAX_PATH as _]; + let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize; + let ret = DragQueryFileW(*hdrop, file_index, Some(&mut buffer)); + if ret == 0 { + log::error!("unable to read file name"); + continue; + } + if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) { + if let Ok(path) = PathBuf::from_str(&file_name) { + paths.push(path); + } + } + } + ReleaseStgMedium(&mut idata); + let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered { + position: Point { + x: Pixels(pt.x as _), + y: Pixels(pt.y as _), + }, + paths: crate::ExternalPaths(paths), + }); + self.0.handle_drag_drop(input); + } else { + *pdweffect = DROPEFFECT_NONE; + } + } + Ok(()) + } + + fn DragOver( + &self, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + _pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending { + position: Point { + x: Pixels(pt.x as _), + y: Pixels(pt.y as _), + }, + }); + self.0.handle_drag_drop(input); + + Ok(()) + } + + fn DragLeave(&self) -> windows::core::Result<()> { + let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited); + self.0.handle_drag_drop(input); + + Ok(()) + } + + fn Drop( + &self, + _pdataobj: Option<&IDataObject>, + _grfkeystate: MODIFIERKEYS_FLAGS, + pt: &POINTL, + _pdweffect: *mut DROPEFFECT, + ) -> windows::core::Result<()> { + let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit { + position: Point { + x: Pixels(pt.x as _), + y: Pixels(pt.y as _), + }, + }); + self.0.handle_drag_drop(input); + + Ok(()) + } +} + fn register_wnd_class() -> PCWSTR { const CLASS_NAME: PCWSTR = w!("Zed::Window"); @@ -923,3 +1069,6 @@ unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize } } + +// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew +const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF; diff --git a/typos.toml b/typos.toml index 91e95e35e9..66a78307dd 100644 --- a/typos.toml +++ b/typos.toml @@ -16,6 +16,8 @@ extend-exclude = [ "crates/editor/src/editor_tests.rs", # Clojure uses .edn filename extension, which is not a misspelling of "end" "crates/languages/src/clojure/config.toml", + # Windows likes it's abbreviations + "crates/gpui/src/platform/windows/", ] [default]