Skip to content

Commit

Permalink
Impl drag-drop action for Windows (#8959)
Browse files Browse the repository at this point in the history
### 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 <[email protected]>
  • Loading branch information
JunkuiZhang and mikayla-maki authored Mar 7, 2024
1 parent e85d484 commit b50f867
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 11 deletions.
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
27 changes: 20 additions & 7 deletions crates/gpui/src/platform/windows/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
Expand Down Expand Up @@ -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::<Runnable>();
let event = unsafe { CreateEventW(None, false, false, None) }.unwrap();
let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event));
Expand Down Expand Up @@ -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<HANDLE> {
LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED).map_err(|e| anyhow!(e))
}
Expand Down
157 changes: 153 additions & 4 deletions crates/gpui/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,38 @@ 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::{
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_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,
Expand Down Expand Up @@ -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)]
Expand All @@ -576,6 +595,7 @@ struct Callbacks {

pub(crate) struct WindowsWindow {
inner: Rc<WindowsWindowInner>,
drag_drop_handler: IDropTarget,
}

struct WindowCreateContext {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -836,6 +875,113 @@ impl PlatformWindow for WindowsWindow {
}
}

#[implement(IDropTarget)]
struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);

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");

Expand Down Expand Up @@ -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;
2 changes: 2 additions & 0 deletions typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit b50f867

Please sign in to comment.