From 0eff6743199517776ff9d016d6a6d7df0838f168 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Wed, 31 May 2023 22:43:30 +0300 Subject: [PATCH 01/20] Janky first version of drag and drop Just recognized as a drop target --- Cargo.toml | 2 +- src/event.rs | 6 ++ src/win/window.rs | 174 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 177 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eb23f4e..af46b66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ xcb-util = { version = "0.3", features = ["icccm"] } nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] -winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] } +winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "winerror"] } uuid = { version = "0.8", features = ["v4"], optional = true } [target.'cfg(target_os="macos")'.dependencies] diff --git a/src/event.rs b/src/event.rs index 3c9ba5b..bdd884a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -75,6 +75,12 @@ pub enum MouseEvent { /// /// May not be available on all platforms. CursorLeft, + + // TODO: Document + DragEntered, + DragMoved, + DragLeft, + DragDropped, } #[derive(Debug, Clone)] diff --git a/src/win/window.rs b/src/win/window.rs index 2af1abe..e9ef08a 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,7 +1,14 @@ -use winapi::shared::guiddef::GUID; -use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; -use winapi::shared::windef::{HWND, RECT}; +use winapi::Interface; +use winapi::shared::guiddef::{GUID, REFIID, IsEqualIID}; +use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM, DWORD}; +use winapi::shared::ntdef::{HRESULT, ULONG}; +use winapi::shared::windef::{HWND, RECT, POINTL}; +use winapi::shared::winerror::{S_OK, E_NOINTERFACE}; use winapi::um::combaseapi::CoCreateGuid; +use winapi::um::objidl::IDataObject; +use winapi::um::ole2::{RegisterDragDrop, OleInitialize}; +use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY}; +use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, @@ -20,9 +27,11 @@ use std::cell::{Cell, RefCell}; use std::collections::VecDeque; use std::ffi::{c_void, OsStr}; use std::marker::PhantomData; +use std::mem::transmute; use std::os::windows::ffi::OsStrExt; use std::ptr::null_mut; use std::rc::Rc; +use std::sync::Arc; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle}; @@ -460,6 +469,7 @@ struct WindowState { handler: RefCell>>, scale_policy: WindowScalePolicy, dw_style: u32, + _drop_target: Option>, /// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably /// borrowing the fields from `WindowState` more than once. For instance, when the window @@ -666,6 +676,7 @@ impl Window<'_> { handler: RefCell::new(None), scale_policy: options.scale, dw_style: flags, + _drop_target: None, deferred_tasks: RefCell::new(VecDeque::with_capacity(4)), @@ -711,7 +722,19 @@ impl Window<'_> { None }; - SetWindowLongPtrW(hwnd, GWLP_USERDATA, Box::into_raw(window_state) as *const _ as _); + let window_state_ptr = Box::into_raw(window_state); + + // TODO: Error handling + OleInitialize(null_mut()); + + let drop_target = Arc::new(DropTarget::new(window_state_ptr)); + + // TODO: Error handling + RegisterDragDrop(hwnd, Arc::as_ptr(&drop_target) as LPDROPTARGET); + + (*window_state_ptr)._drop_target = Some(drop_target); + + SetWindowLongPtrW(hwnd, GWLP_USERDATA, window_state_ptr as *const _ as _); SetTimer(hwnd, WIN_FRAME_TIMER, 15, None); if let Some(mut new_rect) = new_rect { @@ -767,3 +790,146 @@ unsafe impl HasRawWindowHandle for Window<'_> { pub fn copy_to_clipboard(data: &str) { todo!() } + +#[repr(C)] +pub struct DropTarget { + base: IDropTarget, + vtbl: Arc, + + window_state: *mut WindowState, +} + +impl DropTarget { + fn new(window_state: *mut WindowState) -> Self { + let vtbl = Arc::new(IDropTargetVtbl { + parent: IUnknownVtbl { + QueryInterface: Self::query_interface, + AddRef: Self::add_ref, + Release: Self::release, + }, + DragEnter: Self::drag_enter, + DragOver: Self::drag_over, + DragLeave: Self::drag_leave, + Drop: Self::drop, + }); + + Self { + base: IDropTarget { lpVtbl: Arc::as_ptr(&vtbl) }, + vtbl, + + window_state, + } + } + + unsafe extern "system" fn query_interface( + this: *mut IUnknown, + riid: REFIID, + ppvObject: *mut *mut winapi::ctypes::c_void, + ) -> HRESULT + { + println!("query_interface"); + + if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){ + Self::add_ref(this); + *ppvObject = unsafe { transmute(this) }; + return S_OK; + } + + return E_NOINTERFACE; + } + + unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG { + let arc = Arc::from_raw(this); + let result = Arc::strong_count(&arc) + 1; + let _ = Arc::into_raw(arc); + + Arc::increment_strong_count(this); + + result as ULONG + } + + unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG { + let arc = Arc::from_raw(this); + let result = Arc::strong_count(&arc) - 1; + let _ = Arc::into_raw(arc); + + Arc::decrement_strong_count(this); + + result as ULONG + } + + unsafe extern "system" fn drag_enter( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + grfKeyState: DWORD, + pt: *const POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &*(this as *mut DropTarget); + let window_state = &*drop_target.window_state; + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(MouseEvent::DragEntered {}); + window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + + *pdwEffect = DROPEFFECT_COPY; + + S_OK + } + + unsafe extern "system" fn drag_over( + this: *mut IDropTarget, + grfKeyState: DWORD, + pt: *const POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &*(this as *mut DropTarget); + let window_state = &*drop_target.window_state; + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(MouseEvent::DragMoved {}); + window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + + *pdwEffect = DROPEFFECT_COPY; + + S_OK + } + + unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { + let drop_target = &*(this as *mut DropTarget); + let window_state = &*drop_target.window_state; + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(MouseEvent::DragLeft {}); + + window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + + S_OK + } + + unsafe extern "system" fn drop( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + grfKeyState: DWORD, + pt: *const POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &*(this as *mut DropTarget); + let window_state = &*drop_target.window_state; + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(MouseEvent::DragDropped {}); + window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + + *pdwEffect = DROPEFFECT_COPY; + + S_OK + } +} From d8ffd4abc6fc3a21c2a32eebdc21cf33fe0fdfc8 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 3 Jun 2023 22:50:39 +0300 Subject: [PATCH 02/20] Drop effect is now set based on EventStatus --- src/event.rs | 10 ++++++++++ src/win/window.rs | 37 ++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/event.rs b/src/event.rs index bdd884a..594905f 100644 --- a/src/event.rs +++ b/src/event.rs @@ -98,6 +98,14 @@ pub enum Event { Window(WindowEvent), } +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DropEffect { + Copy, + Move, + Link, + Scroll, +} + /// Return value for [WindowHandler::on_event](`crate::WindowHandler::on_event()`), /// indicating whether the event was handled by your window or should be passed /// back to the platform. @@ -117,4 +125,6 @@ pub enum EventStatus { /// DAW functionality for playing piano keys with the keyboard while a /// plugin window is in focus. Ignored, + // TODO: Document + AcceptDrop(DropEffect), } diff --git a/src/win/window.rs b/src/win/window.rs index e9ef08a..76b56d4 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -7,7 +7,7 @@ use winapi::shared::winerror::{S_OK, E_NOINTERFACE}; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::objidl::IDataObject; use winapi::um::ole2::{RegisterDragDrop, OleInitialize}; -use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY}; +use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY, DROPEFFECT_NONE, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL}; use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, @@ -39,7 +39,7 @@ const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, }; use super::keyboard::KeyboardState; @@ -872,9 +872,14 @@ impl DropTarget { let mut window = crate::Window::new(&mut window); let event = Event::Mouse(MouseEvent::DragEntered {}); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - *pdwEffect = DROPEFFECT_COPY; + let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, + EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, + EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, + EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, + _ => *pdwEffect = DROPEFFECT_NONE, + } S_OK } @@ -892,9 +897,14 @@ impl DropTarget { let mut window = crate::Window::new(&mut window); let event = Event::Mouse(MouseEvent::DragMoved {}); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - *pdwEffect = DROPEFFECT_COPY; + let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, + EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, + EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, + EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, + _ => *pdwEffect = DROPEFFECT_NONE, + } S_OK } @@ -926,9 +936,14 @@ impl DropTarget { let mut window = crate::Window::new(&mut window); let event = Event::Mouse(MouseEvent::DragDropped {}); - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - *pdwEffect = DROPEFFECT_COPY; + let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, + EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, + EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, + EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, + _ => *pdwEffect = DROPEFFECT_NONE, + } S_OK } From f5840bc65bf32223618090f4de5d9ae9c5dd5310 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sun, 4 Jun 2023 21:39:59 +0300 Subject: [PATCH 03/20] Refactor away duplicated code --- src/win/window.rs | 93 +++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 76b56d4..4f9ab8c 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -821,6 +821,28 @@ impl DropTarget { } } + fn on_event(this: *mut IDropTarget, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { + unsafe { + let drop_target = &*(this as *mut DropTarget); + let window_state = &*drop_target.window_state; + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(event); + let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); + + if let Some(pdwEffect) = pdwEffect { + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, + EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, + EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, + EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, + _ => *pdwEffect = DROPEFFECT_NONE, + } + } + } + } + unsafe extern "system" fn query_interface( this: *mut IUnknown, riid: REFIID, @@ -857,7 +879,7 @@ impl DropTarget { result as ULONG } - + unsafe extern "system" fn drag_enter( this: *mut IDropTarget, pDataObj: *const IDataObject, @@ -866,21 +888,7 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let drop_target = &*(this as *mut DropTarget); - let window_state = &*drop_target.window_state; - let mut window = window_state.create_window(); - let mut window = crate::Window::new(&mut window); - - let event = Event::Mouse(MouseEvent::DragEntered {}); - let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - match event_status { - EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, - EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, - EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, - EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, - _ => *pdwEffect = DROPEFFECT_NONE, - } - + Self::on_event(this, Some(pdwEffect), MouseEvent::DragEntered {}); S_OK } @@ -891,34 +899,12 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let drop_target = &*(this as *mut DropTarget); - let window_state = &*drop_target.window_state; - let mut window = window_state.create_window(); - let mut window = crate::Window::new(&mut window); - - let event = Event::Mouse(MouseEvent::DragMoved {}); - let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - match event_status { - EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, - EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, - EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, - EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, - _ => *pdwEffect = DROPEFFECT_NONE, - } - + Self::on_event(this, Some(pdwEffect), MouseEvent::DragMoved {}); S_OK } unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { - let drop_target = &*(this as *mut DropTarget); - let window_state = &*drop_target.window_state; - let mut window = window_state.create_window(); - let mut window = crate::Window::new(&mut window); - - let event = Event::Mouse(MouseEvent::DragLeft {}); - - window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - + Self::on_event(this, None, MouseEvent::DragLeft {}); S_OK } @@ -930,21 +916,18 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let drop_target = &*(this as *mut DropTarget); - let window_state = &*drop_target.window_state; - let mut window = window_state.create_window(); - let mut window = crate::Window::new(&mut window); - - let event = Event::Mouse(MouseEvent::DragDropped {}); - let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - match event_status { - EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, - EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, - EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, - EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, - _ => *pdwEffect = DROPEFFECT_NONE, - } - + Self::on_event(this, Some(pdwEffect), MouseEvent::DragDropped {}); S_OK } } + +impl DropEffect { + pub fn to_dword(&self) -> DWORD { + match self { + DropEffect::Copy => DROPEFFECT_COPY, + DropEffect::Move => DROPEFFECT_MOVE, + DropEffect::Link => DROPEFFECT_LINK, + DropEffect::Scroll => DROPEFFECT_SCROLL, + } + } +} From ef64cae5383952eec3783750fd1662a5b662023f Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sun, 4 Jun 2023 21:40:45 +0300 Subject: [PATCH 04/20] Delete some unused lines committed by accident --- src/win/window.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 4f9ab8c..3c9c336 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -920,14 +920,3 @@ impl DropTarget { S_OK } } - -impl DropEffect { - pub fn to_dword(&self) -> DWORD { - match self { - DropEffect::Copy => DROPEFFECT_COPY, - DropEffect::Move => DROPEFFECT_MOVE, - DropEffect::Link => DROPEFFECT_LINK, - DropEffect::Scroll => DROPEFFECT_SCROLL, - } - } -} From b5f39399302a25ea436ccbf2809a87fe21a185ea Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sun, 4 Jun 2023 23:43:10 +0300 Subject: [PATCH 05/20] Parse drop data Only handling files for now --- Cargo.toml | 2 +- src/event.rs | 19 ++++++++++-- src/win/window.rs | 73 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index af46b66..162fe60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ xcb-util = { version = "0.3", features = ["icccm"] } nix = "0.22.0" [target.'cfg(target_os="windows")'.dependencies] -winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "winerror"] } +winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi", "ole2", "oleidl", "shellapi", "winerror"] } uuid = { version = "0.8", features = ["v4"], optional = true } [target.'cfg(target_os="macos")'.dependencies] diff --git a/src/event.rs b/src/event.rs index 594905f..b7ce911 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use keyboard_types::{KeyboardEvent, Modifiers}; use crate::{Point, WindowInfo}; @@ -32,7 +34,7 @@ pub enum ScrollDelta { }, } -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum MouseEvent { /// The mouse cursor was moved CursorMoved { @@ -77,10 +79,16 @@ pub enum MouseEvent { CursorLeft, // TODO: Document - DragEntered, + DragEntered { + data: Option, + }, + DragMoved, DragLeft, - DragDropped, + + DragDropped { + data: Option, + }, } #[derive(Debug, Clone)] @@ -106,6 +114,11 @@ pub enum DropEffect { Scroll, } +#[derive(Debug, Clone, PartialEq)] +pub enum DropData { + Files(Vec), +} + /// Return value for [WindowHandler::on_event](`crate::WindowHandler::on_event()`), /// indicating whether the event was handled by your window or should be passed /// back to the platform. diff --git a/src/win/window.rs b/src/win/window.rs index 3c9c336..7a3181c 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -4,10 +4,12 @@ use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM, DWOR use winapi::shared::ntdef::{HRESULT, ULONG}; use winapi::shared::windef::{HWND, RECT, POINTL}; use winapi::shared::winerror::{S_OK, E_NOINTERFACE}; +use winapi::shared::wtypes::DVASPECT_CONTENT; use winapi::um::combaseapi::CoCreateGuid; -use winapi::um::objidl::IDataObject; +use winapi::um::objidl::{IDataObject, STGMEDIUM, FORMATETC, TYMED_HGLOBAL}; use winapi::um::ole2::{RegisterDragDrop, OleInitialize}; use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY, DROPEFFECT_NONE, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL}; +use winapi::um::shellapi::DragQueryFileW; use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, @@ -20,15 +22,16 @@ use winapi::um::winuser::{ WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, - XBUTTON1, XBUTTON2, + XBUTTON1, XBUTTON2, CF_HDROP, }; use std::cell::{Cell, RefCell}; use std::collections::VecDeque; -use std::ffi::{c_void, OsStr}; +use std::ffi::{c_void, OsStr, OsString}; use std::marker::PhantomData; use std::mem::transmute; use std::os::windows::ffi::OsStrExt; +use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; use std::rc::Rc; use std::sync::Arc; @@ -39,7 +42,7 @@ const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, DropData, }; use super::keyboard::KeyboardState; @@ -843,6 +846,49 @@ impl DropTarget { } } + fn parse_drop_data(data_object: &IDataObject) -> Option { + let format = FORMATETC { + cfFormat: CF_HDROP as u16, + ptd: null_mut(), + dwAspect: DVASPECT_CONTENT, + lindex: -1, + tymed: TYMED_HGLOBAL, + }; + + let mut medium = STGMEDIUM { + tymed: 0, + u: null_mut(), + pUnkForRelease: null_mut(), + }; + + unsafe { + let hresult = data_object.GetData(&format, &mut medium); + assert!(hresult == S_OK); // TODO: Error handling + + let hdrop = transmute((*medium.u).hGlobal()); + + let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0); + if item_count == 0 { + return None; + } + + let mut paths = Vec::with_capacity(item_count as usize); + + for i in 0..item_count { + let characters = DragQueryFileW(hdrop, i, null_mut(), 0); + let buffer_size = characters as usize + 1; + let mut buffer = Vec::::with_capacity(buffer_size); + + DragQueryFileW(hdrop, i, transmute(buffer.spare_capacity_mut().as_mut_ptr()), buffer_size as u32); + buffer.set_len(buffer_size); + + paths.push(OsString::from_wide(&buffer[..characters as usize]).into()) + } + + Some(DropData::Files(paths)) + } + } + unsafe extern "system" fn query_interface( this: *mut IUnknown, riid: REFIID, @@ -888,7 +934,12 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - Self::on_event(this, Some(pdwEffect), MouseEvent::DragEntered {}); + let data = Self::parse_drop_data(&*pDataObj); + let event = MouseEvent::DragEntered { + data + }; + + Self::on_event(this, Some(pdwEffect), event); S_OK } @@ -899,12 +950,13 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - Self::on_event(this, Some(pdwEffect), MouseEvent::DragMoved {}); + let event = MouseEvent::DragMoved {}; + Self::on_event(this, Some(pdwEffect), event); S_OK } unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { - Self::on_event(this, None, MouseEvent::DragLeft {}); + Self::on_event(this, None, MouseEvent::DragLeft); S_OK } @@ -916,7 +968,12 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - Self::on_event(this, Some(pdwEffect), MouseEvent::DragDropped {}); + let data = Self::parse_drop_data(&*pDataObj); + let event = MouseEvent::DragDropped { + data + }; + + Self::on_event(this, Some(pdwEffect), event); S_OK } } From 1c50c326cb9ea822dd2a956008565dd04c6060fc Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 20:25:27 +0300 Subject: [PATCH 06/20] Add drop data to all drag events Add DragData::None instead of using Option --- src/event.rs | 14 +++++++---- src/win/window.rs | 59 ++++++++++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/event.rs b/src/event.rs index b7ce911..46577ec 100644 --- a/src/event.rs +++ b/src/event.rs @@ -80,14 +80,19 @@ pub enum MouseEvent { // TODO: Document DragEntered { - data: Option, + data: DropData, }, - DragMoved, - DragLeft, + DragMoved { + data: DropData, + }, + + DragLeft { + data: DropData, + }, DragDropped { - data: Option, + data: DropData, }, } @@ -116,6 +121,7 @@ pub enum DropEffect { #[derive(Debug, Clone, PartialEq)] pub enum DropData { + None, Files(Vec), } diff --git a/src/win/window.rs b/src/win/window.rs index 7a3181c..b41d68b 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -800,6 +800,11 @@ pub struct DropTarget { vtbl: Arc, window_state: *mut WindowState, + + // Drop data is cached since DragOver callbacks don't provide drop data + // and handling drag move events gets awkward without having access to + // drop data + drop_data: DropData, } impl DropTarget { @@ -821,13 +826,14 @@ impl DropTarget { vtbl, window_state, + + drop_data: DropData::None, } } - fn on_event(this: *mut IDropTarget, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { + fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { unsafe { - let drop_target = &*(this as *mut DropTarget); - let window_state = &*drop_target.window_state; + let window_state = &*self.window_state; let mut window = window_state.create_window(); let mut window = crate::Window::new(&mut window); @@ -846,7 +852,7 @@ impl DropTarget { } } - fn parse_drop_data(data_object: &IDataObject) -> Option { + fn parse_drop_data(&mut self, data_object: &IDataObject) { let format = FORMATETC { cfFormat: CF_HDROP as u16, ptd: null_mut(), @@ -863,13 +869,17 @@ impl DropTarget { unsafe { let hresult = data_object.GetData(&format, &mut medium); - assert!(hresult == S_OK); // TODO: Error handling + if hresult != S_OK { + self.drop_data = DropData::None; + return; + } let hdrop = transmute((*medium.u).hGlobal()); let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0); if item_count == 0 { - return None; + self.drop_data = DropData::None; + return; } let mut paths = Vec::with_capacity(item_count as usize); @@ -885,7 +895,7 @@ impl DropTarget { paths.push(OsString::from_wide(&buffer[..characters as usize]).into()) } - Some(DropData::Files(paths)) + self.drop_data = DropData::Files(paths); } } @@ -895,8 +905,6 @@ impl DropTarget { ppvObject: *mut *mut winapi::ctypes::c_void, ) -> HRESULT { - println!("query_interface"); - if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){ Self::add_ref(this); *ppvObject = unsafe { transmute(this) }; @@ -934,12 +942,14 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let data = Self::parse_drop_data(&*pDataObj); + let drop_target = &mut *(this as *mut DropTarget); + + drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragEntered { - data + data: drop_target.drop_data.clone(), }; - Self::on_event(this, Some(pdwEffect), event); + drop_target.on_event(Some(pdwEffect), event); S_OK } @@ -950,13 +960,24 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let event = MouseEvent::DragMoved {}; - Self::on_event(this, Some(pdwEffect), event); + let drop_target = &mut *(this as *mut DropTarget); + + let event = MouseEvent::DragMoved { + data: drop_target.drop_data.clone(), + }; + + drop_target.on_event(Some(pdwEffect), event); S_OK } unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { - Self::on_event(this, None, MouseEvent::DragLeft); + let drop_target = &mut *(this as *mut DropTarget); + + let event = MouseEvent::DragLeft { + data: drop_target.drop_data.clone(), + }; + + drop_target.on_event(None, event); S_OK } @@ -968,12 +989,14 @@ impl DropTarget { pdwEffect: *mut DWORD, ) -> HRESULT { - let data = Self::parse_drop_data(&*pDataObj); + let drop_target = &mut *(this as *mut DropTarget); + + drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragDropped { - data + data: drop_target.drop_data.clone(), }; - Self::on_event(this, Some(pdwEffect), event); + drop_target.on_event(Some(pdwEffect), event); S_OK } } From 1a8484bc554513c078ba1ca4e5b6abc68e36c478 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 20:48:06 +0300 Subject: [PATCH 07/20] Add coordinates to drag events --- src/event.rs | 4 ++++ src/win/window.rs | 31 +++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/event.rs b/src/event.rs index 46577ec..8942546 100644 --- a/src/event.rs +++ b/src/event.rs @@ -80,18 +80,22 @@ pub enum MouseEvent { // TODO: Document DragEntered { + coordinates: (i32, i32), data: DropData, }, DragMoved { + coordinates: (i32, i32), data: DropData, }, DragLeft { + coordinates: (i32, i32), data: DropData, }, DragDropped { + coordinates: (i32, i32), data: DropData, }, } diff --git a/src/win/window.rs b/src/win/window.rs index b41d68b..f1736a9 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -801,9 +801,9 @@ pub struct DropTarget { window_state: *mut WindowState, - // Drop data is cached since DragOver callbacks don't provide drop data - // and handling drag move events gets awkward without having access to - // drop data + // These are cached since DragOver and DragLeave callbacks don't provide them, + // and handling drag move events gets awkward on the client end otherwise + drag_coordinates: (i32, i32), drop_data: DropData, } @@ -827,6 +827,7 @@ impl DropTarget { window_state, + drag_coordinates: (0, 0), drop_data: DropData::None, } } @@ -852,6 +853,18 @@ impl DropTarget { } } + fn parse_coordinates(&mut self, pt: *const POINTL) { + // There's a bug in winapi: the POINTL pointer should actually be a POINTL structure + // This happens to work on 64-bit platforms because two c_longs (that translate to + // 32-bit signed integers) happen to be the same size as a 64-bit pointer... + // For now, just hack around that bug + + let x = pt as i64 & u32::MAX as i64; + let y = pt as i64 >> 32; + + self.drag_coordinates = (x as i32, y as i32) + } + fn parse_drop_data(&mut self, data_object: &IDataObject) { let format = FORMATETC { cfFormat: CF_HDROP as u16, @@ -943,9 +956,12 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - + + drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); + let event = MouseEvent::DragEntered { + coordinates: drop_target.drag_coordinates, data: drop_target.drop_data.clone(), }; @@ -962,7 +978,10 @@ impl DropTarget { { let drop_target = &mut *(this as *mut DropTarget); + drop_target.parse_coordinates(pt); + let event = MouseEvent::DragMoved { + coordinates: drop_target.drag_coordinates, data: drop_target.drop_data.clone(), }; @@ -974,6 +993,7 @@ impl DropTarget { let drop_target = &mut *(this as *mut DropTarget); let event = MouseEvent::DragLeft { + coordinates: drop_target.drag_coordinates, data: drop_target.drop_data.clone(), }; @@ -991,8 +1011,11 @@ impl DropTarget { { let drop_target = &mut *(this as *mut DropTarget); + drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); + let event = MouseEvent::DragDropped { + coordinates: drop_target.drag_coordinates, data: drop_target.drop_data.clone(), }; From e9a1460a5b3eaeec1817f23a131b5333298dbc94 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 20:53:25 +0300 Subject: [PATCH 08/20] Use Point for drag coordinates like other mouse events --- src/event.rs | 8 ++++---- src/win/window.rs | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/event.rs b/src/event.rs index 8942546..c3882e8 100644 --- a/src/event.rs +++ b/src/event.rs @@ -80,22 +80,22 @@ pub enum MouseEvent { // TODO: Document DragEntered { - coordinates: (i32, i32), + position: Point, data: DropData, }, DragMoved { - coordinates: (i32, i32), + position: Point, data: DropData, }, DragLeft { - coordinates: (i32, i32), + position: Point, data: DropData, }, DragDropped { - coordinates: (i32, i32), + position: Point, data: DropData, }, } diff --git a/src/win/window.rs b/src/win/window.rs index f1736a9..71045c2 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -42,7 +42,7 @@ const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, DropData, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, DropData, Point, }; use super::keyboard::KeyboardState; @@ -803,7 +803,7 @@ pub struct DropTarget { // These are cached since DragOver and DragLeave callbacks don't provide them, // and handling drag move events gets awkward on the client end otherwise - drag_coordinates: (i32, i32), + drag_position: Point, drop_data: DropData, } @@ -827,7 +827,7 @@ impl DropTarget { window_state, - drag_coordinates: (0, 0), + drag_position: Point::new(0.0, 0.0), drop_data: DropData::None, } } @@ -858,11 +858,13 @@ impl DropTarget { // This happens to work on 64-bit platforms because two c_longs (that translate to // 32-bit signed integers) happen to be the same size as a 64-bit pointer... // For now, just hack around that bug + let window_state = unsafe { &*self.window_state }; let x = pt as i64 & u32::MAX as i64; let y = pt as i64 >> 32; - self.drag_coordinates = (x as i32, y as i32) + let phy_point = PhyPoint::new(x as i32, y as i32); + self.drag_position = phy_point.to_logical(&window_state.window_info.borrow()) } fn parse_drop_data(&mut self, data_object: &IDataObject) { @@ -961,7 +963,7 @@ impl DropTarget { drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragEntered { - coordinates: drop_target.drag_coordinates, + position: drop_target.drag_position, data: drop_target.drop_data.clone(), }; @@ -981,7 +983,7 @@ impl DropTarget { drop_target.parse_coordinates(pt); let event = MouseEvent::DragMoved { - coordinates: drop_target.drag_coordinates, + position: drop_target.drag_position, data: drop_target.drop_data.clone(), }; @@ -993,7 +995,7 @@ impl DropTarget { let drop_target = &mut *(this as *mut DropTarget); let event = MouseEvent::DragLeft { - coordinates: drop_target.drag_coordinates, + position: drop_target.drag_position, data: drop_target.drop_data.clone(), }; @@ -1015,7 +1017,7 @@ impl DropTarget { drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragDropped { - coordinates: drop_target.drag_coordinates, + position: drop_target.drag_position, data: drop_target.drop_data.clone(), }; From fe03957514aec10f206be9f64ff227598e589a74 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 21:00:19 +0300 Subject: [PATCH 09/20] Add keyboard modifiers to drag events Remove fields from DragLeft --- src/event.rs | 14 ++++++++++---- src/win/window.rs | 23 ++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/event.rs b/src/event.rs index c3882e8..f83a75e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -80,22 +80,28 @@ pub enum MouseEvent { // TODO: Document DragEntered { + /// The logical coordinates of the mouse position position: Point, + /// The modifiers that were held down just before the event. + modifiers: Modifiers, data: DropData, }, DragMoved { + /// The logical coordinates of the mouse position position: Point, + /// The modifiers that were held down just before the event. + modifiers: Modifiers, data: DropData, }, - DragLeft { - position: Point, - data: DropData, - }, + DragLeft, DragDropped { + /// The logical coordinates of the mouse position position: Point, + /// The modifiers that were held down just before the event. + modifiers: Modifiers, data: DropData, }, } diff --git a/src/win/window.rs b/src/win/window.rs index 71045c2..8efac98 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -958,12 +958,17 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); + let window_state = unsafe { &*drop_target.window_state }; drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragEntered { position: drop_target.drag_position, + modifiers: window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), data: drop_target.drop_data.clone(), }; @@ -979,11 +984,16 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); + let window_state = unsafe { &*drop_target.window_state }; drop_target.parse_coordinates(pt); let event = MouseEvent::DragMoved { position: drop_target.drag_position, + modifiers: window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), data: drop_target.drop_data.clone(), }; @@ -993,13 +1003,7 @@ impl DropTarget { unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - - let event = MouseEvent::DragLeft { - position: drop_target.drag_position, - data: drop_target.drop_data.clone(), - }; - - drop_target.on_event(None, event); + drop_target.on_event(None, MouseEvent::DragLeft); S_OK } @@ -1012,12 +1016,17 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); + let window_state = unsafe { &*drop_target.window_state }; drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragDropped { position: drop_target.drag_position, + modifiers: window_state + .keyboard_state + .borrow() + .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), data: drop_target.drop_data.clone(), }; From ab673fe5340181d3a3fa661c8164d786971f9720 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 22:20:07 +0300 Subject: [PATCH 10/20] Comment cleanup --- src/event.rs | 4 +++- src/win/window.rs | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/event.rs b/src/event.rs index f83a75e..d13d8aa 100644 --- a/src/event.rs +++ b/src/event.rs @@ -78,12 +78,12 @@ pub enum MouseEvent { /// May not be available on all platforms. CursorLeft, - // TODO: Document DragEntered { /// The logical coordinates of the mouse position position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, + /// Data being dragged data: DropData, }, @@ -92,6 +92,7 @@ pub enum MouseEvent { position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, + /// Data being dragged data: DropData, }, @@ -102,6 +103,7 @@ pub enum MouseEvent { position: Point, /// The modifiers that were held down just before the event. modifiers: Modifiers, + /// Data being dragged data: DropData, }, } diff --git a/src/win/window.rs b/src/win/window.rs index 8efac98..04e998f 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -726,13 +726,9 @@ impl Window<'_> { }; let window_state_ptr = Box::into_raw(window_state); - - // TODO: Error handling - OleInitialize(null_mut()); - let drop_target = Arc::new(DropTarget::new(window_state_ptr)); - // TODO: Error handling + OleInitialize(null_mut()); RegisterDragDrop(hwnd, Arc::as_ptr(&drop_target) as LPDROPTARGET); (*window_state_ptr)._drop_target = Some(drop_target); From 92d7560f4b106924653e238de45d80451275850c Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Mon, 5 Jun 2023 22:24:29 +0300 Subject: [PATCH 11/20] Document EventStatus::AcceptDrop --- src/event.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/event.rs b/src/event.rs index d13d8aa..04d4908 100644 --- a/src/event.rs +++ b/src/event.rs @@ -156,6 +156,7 @@ pub enum EventStatus { /// DAW functionality for playing piano keys with the keyboard while a /// plugin window is in focus. Ignored, - // TODO: Document + /// We are prepared to handle the data in the drag and dropping will + /// result in [DropEffect] AcceptDrop(DropEffect), } From e47f25facdc45219b69edf3b7e297bbe23aa2491 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 21:52:44 +0300 Subject: [PATCH 12/20] DropTargetVtbl instance is const now --- src/win/window.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 04e998f..289ec65 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -793,7 +793,6 @@ pub fn copy_to_clipboard(data: &str) { #[repr(C)] pub struct DropTarget { base: IDropTarget, - vtbl: Arc, window_state: *mut WindowState, @@ -803,23 +802,22 @@ pub struct DropTarget { drop_data: DropData, } +const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { + parent: IUnknownVtbl { + QueryInterface: DropTarget::query_interface, + AddRef: DropTarget::add_ref, + Release: DropTarget::release, + }, + DragEnter: DropTarget::drag_enter, + DragOver: DropTarget::drag_over, + DragLeave: DropTarget::drag_leave, + Drop: DropTarget::drop, +}; + impl DropTarget { fn new(window_state: *mut WindowState) -> Self { - let vtbl = Arc::new(IDropTargetVtbl { - parent: IUnknownVtbl { - QueryInterface: Self::query_interface, - AddRef: Self::add_ref, - Release: Self::release, - }, - DragEnter: Self::drag_enter, - DragOver: Self::drag_over, - DragLeave: Self::drag_leave, - Drop: Self::drop, - }); - Self { - base: IDropTarget { lpVtbl: Arc::as_ptr(&vtbl) }, - vtbl, + base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, window_state, From 4f0a71736c7534b41693942a3ae3a0737dadb437 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 21:58:31 +0300 Subject: [PATCH 13/20] Remove unnecessary transmute --- src/win/window.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win/window.rs b/src/win/window.rs index 289ec65..4526b9f 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -916,7 +916,7 @@ impl DropTarget { { if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){ Self::add_ref(this); - *ppvObject = unsafe { transmute(this) }; + *ppvObject = this as *mut winapi::ctypes::c_void; return S_OK; } From 2019efde6513571d6bff8d8b766402dd0722cc31 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 22:01:23 +0300 Subject: [PATCH 14/20] Use Rc instead of Arc for DropTarget --- src/win/window.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 4526b9f..fc2c201 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -472,7 +472,7 @@ struct WindowState { handler: RefCell>>, scale_policy: WindowScalePolicy, dw_style: u32, - _drop_target: Option>, + _drop_target: Option>, /// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably /// borrowing the fields from `WindowState` more than once. For instance, when the window @@ -726,10 +726,10 @@ impl Window<'_> { }; let window_state_ptr = Box::into_raw(window_state); - let drop_target = Arc::new(DropTarget::new(window_state_ptr)); + let drop_target = Rc::new(DropTarget::new(window_state_ptr)); OleInitialize(null_mut()); - RegisterDragDrop(hwnd, Arc::as_ptr(&drop_target) as LPDROPTARGET); + RegisterDragDrop(hwnd, Rc::as_ptr(&drop_target) as LPDROPTARGET); (*window_state_ptr)._drop_target = Some(drop_target); @@ -924,21 +924,21 @@ impl DropTarget { } unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG { - let arc = Arc::from_raw(this); - let result = Arc::strong_count(&arc) + 1; - let _ = Arc::into_raw(arc); + let arc = Rc::from_raw(this); + let result = Rc::strong_count(&arc) + 1; + let _ = Rc::into_raw(arc); - Arc::increment_strong_count(this); + Rc::increment_strong_count(this); result as ULONG } unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG { - let arc = Arc::from_raw(this); - let result = Arc::strong_count(&arc) - 1; - let _ = Arc::into_raw(arc); + let arc = Rc::from_raw(this); + let result = Rc::strong_count(&arc) - 1; + let _ = Rc::into_raw(arc); - Arc::decrement_strong_count(this); + Rc::decrement_strong_count(this); result as ULONG } From eb3a02115a7cf8a0828680f46630c655338e2ad8 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 22:21:37 +0300 Subject: [PATCH 15/20] Change how DragTarget functions parse coordinates Instead of working around the winapi bug by manually parsing the pointer we're (incorrectly) given, use the correct function signature and transmute the function pointer --- src/win/window.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index fc2c201..d3d3260 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -34,7 +34,6 @@ use std::os::windows::ffi::OsStrExt; use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; use std::rc::Rc; -use std::sync::Arc; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle}; @@ -802,16 +801,20 @@ pub struct DropTarget { drop_data: DropData, } +// These function pointers have to be stored in a (const) variable before they can be transmuted +const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter; +const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over; +const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop; const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { parent: IUnknownVtbl { QueryInterface: DropTarget::query_interface, AddRef: DropTarget::add_ref, Release: DropTarget::release, }, - DragEnter: DropTarget::drag_enter, - DragOver: DropTarget::drag_over, + DragEnter: unsafe { transmute(DRAG_ENTER_PTR) }, + DragOver: unsafe { transmute(DRAG_OVER_PTR) }, DragLeave: DropTarget::drag_leave, - Drop: DropTarget::drop, + Drop: unsafe { transmute(DROP_PTR) }, }; impl DropTarget { @@ -847,17 +850,10 @@ impl DropTarget { } } - fn parse_coordinates(&mut self, pt: *const POINTL) { - // There's a bug in winapi: the POINTL pointer should actually be a POINTL structure - // This happens to work on 64-bit platforms because two c_longs (that translate to - // 32-bit signed integers) happen to be the same size as a 64-bit pointer... - // For now, just hack around that bug + fn parse_coordinates(&mut self, pt: POINTL) { let window_state = unsafe { &*self.window_state }; - let x = pt as i64 & u32::MAX as i64; - let y = pt as i64 >> 32; - - let phy_point = PhyPoint::new(x as i32, y as i32); + let phy_point = PhyPoint::new(pt.x, pt.y); self.drag_position = phy_point.to_logical(&window_state.window_info.borrow()) } @@ -947,7 +943,7 @@ impl DropTarget { this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut DWORD, ) -> HRESULT { @@ -973,7 +969,7 @@ impl DropTarget { unsafe extern "system" fn drag_over( this: *mut IDropTarget, grfKeyState: DWORD, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut DWORD, ) -> HRESULT { @@ -1005,7 +1001,7 @@ impl DropTarget { this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, - pt: *const POINTL, + pt: POINTL, pdwEffect: *mut DWORD, ) -> HRESULT { From 481bf73293385475c9e050fb0940ae474e8498c6 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 22:47:22 +0300 Subject: [PATCH 16/20] Clean up ownership around WindowState and DropTarget - WindowState no longer holds a reference to DropTarget - DropTarget is passed to RegisterDragDrop() with Rc::into_raw() instead of Rc::as_ptr() so it keeps the reference - WindowState is created with Rc instead of Box so DropTarget can hold a Rc to it --- src/win/window.rs | 64 ++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index d3d3260..3d9c2dd 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -160,7 +160,7 @@ unsafe extern "system" fn wnd_proc( if msg == WM_NCDESTROY { unregister_wnd_class((*window_state_ptr).window_class); SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); - drop(Box::from_raw(window_state_ptr)); + drop(Rc::from_raw(window_state_ptr)); } // The actual custom window proc has been moved to another function so we can always handle @@ -471,7 +471,6 @@ struct WindowState { handler: RefCell>>, scale_policy: WindowScalePolicy, dw_style: u32, - _drop_target: Option>, /// Tasks that should be executed at the end of `wnd_proc`. This is needed to avoid mutably /// borrowing the fields from `WindowState` more than once. For instance, when the window @@ -666,7 +665,7 @@ impl Window<'_> { let (parent_handle, window_handle) = ParentHandle::new(hwnd); let parent_handle = if parented { Some(parent_handle) } else { None }; - let window_state = Box::new(WindowState { + let window_state = Rc::new(WindowState { hwnd, window_class, window_info: RefCell::new(window_info), @@ -678,7 +677,6 @@ impl Window<'_> { handler: RefCell::new(None), scale_policy: options.scale, dw_style: flags, - _drop_target: None, deferred_tasks: RefCell::new(VecDeque::with_capacity(4)), @@ -724,15 +722,12 @@ impl Window<'_> { None }; - let window_state_ptr = Box::into_raw(window_state); - let drop_target = Rc::new(DropTarget::new(window_state_ptr)); + let drop_target = Rc::new(DropTarget::new(window_state.clone())); OleInitialize(null_mut()); - RegisterDragDrop(hwnd, Rc::as_ptr(&drop_target) as LPDROPTARGET); + RegisterDragDrop(hwnd, Rc::into_raw(drop_target) as LPDROPTARGET); - (*window_state_ptr)._drop_target = Some(drop_target); - - SetWindowLongPtrW(hwnd, GWLP_USERDATA, window_state_ptr as *const _ as _); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, Rc::into_raw(window_state) as *const _ as _); SetTimer(hwnd, WIN_FRAME_TIMER, 15, None); if let Some(mut new_rect) = new_rect { @@ -789,18 +784,6 @@ pub fn copy_to_clipboard(data: &str) { todo!() } -#[repr(C)] -pub struct DropTarget { - base: IDropTarget, - - window_state: *mut WindowState, - - // These are cached since DragOver and DragLeave callbacks don't provide them, - // and handling drag move events gets awkward on the client end otherwise - drag_position: Point, - drop_data: DropData, -} - // These function pointers have to be stored in a (const) variable before they can be transmuted const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter; const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over; @@ -817,8 +800,20 @@ const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { Drop: unsafe { transmute(DROP_PTR) }, }; +#[repr(C)] +pub struct DropTarget { + base: IDropTarget, + + window_state: Rc, + + // These are cached since DragOver and DragLeave callbacks don't provide them, + // and handling drag move events gets awkward on the client end otherwise + drag_position: Point, + drop_data: DropData, +} + impl DropTarget { - fn new(window_state: *mut WindowState) -> Self { + fn new(window_state: Rc) -> Self { Self { base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, @@ -948,17 +943,14 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let window_state = unsafe { &*drop_target.window_state }; - + let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragEntered { position: drop_target.drag_position, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), + modifiers, data: drop_target.drop_data.clone(), }; @@ -974,16 +966,13 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let window_state = unsafe { &*drop_target.window_state }; + let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); let event = MouseEvent::DragMoved { position: drop_target.drag_position, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), + modifiers, data: drop_target.drop_data.clone(), }; @@ -1006,17 +995,14 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let window_state = unsafe { &*drop_target.window_state }; + let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); let event = MouseEvent::DragDropped { position: drop_target.drag_position, - modifiers: window_state - .keyboard_state - .borrow() - .get_modifiers_from_mouse_wparam(grfKeyState as WPARAM), + modifiers, data: drop_target.drop_data.clone(), }; From 9028321012e3ad07e622f84e5318907a3fd9acbc Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 23:00:58 +0300 Subject: [PATCH 17/20] Call RevokeDragDrop() before dropping WindowState --- src/win/window.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/win/window.rs b/src/win/window.rs index 3d9c2dd..47b2161 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -8,6 +8,7 @@ use winapi::shared::wtypes::DVASPECT_CONTENT; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::objidl::{IDataObject, STGMEDIUM, FORMATETC, TYMED_HGLOBAL}; use winapi::um::ole2::{RegisterDragDrop, OleInitialize}; +use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop}; use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY, DROPEFFECT_NONE, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL}; use winapi::um::shellapi::DragQueryFileW; use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; @@ -158,6 +159,7 @@ unsafe extern "system" fn wnd_proc( // NOTE: This is not handled in `wnd_proc_inner` because of the deferred task loop above if msg == WM_NCDESTROY { + RevokeDragDrop(hwnd); unregister_wnd_class((*window_state_ptr).window_class); SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0); drop(Rc::from_raw(window_state_ptr)); From b33398703fa1c28e700b720f792a995338027375 Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 23:07:11 +0300 Subject: [PATCH 18/20] Walk back the previous restructuring a bit We were leaking the DropTarget, so WindowState holds a reference to DropTarget again, so it can be dropped when the window is destroyed. Also, DropTarget now holds a Weak reference to WindowState and that's taken into account in callbacks. --- src/win/window.rs | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/win/window.rs b/src/win/window.rs index 47b2161..cd73357 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -3,11 +3,10 @@ use winapi::shared::guiddef::{GUID, REFIID, IsEqualIID}; use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM, DWORD}; use winapi::shared::ntdef::{HRESULT, ULONG}; use winapi::shared::windef::{HWND, RECT, POINTL}; -use winapi::shared::winerror::{S_OK, E_NOINTERFACE}; +use winapi::shared::winerror::{S_OK, E_NOINTERFACE, E_UNEXPECTED}; use winapi::shared::wtypes::DVASPECT_CONTENT; use winapi::um::combaseapi::CoCreateGuid; use winapi::um::objidl::{IDataObject, STGMEDIUM, FORMATETC, TYMED_HGLOBAL}; -use winapi::um::ole2::{RegisterDragDrop, OleInitialize}; use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop}; use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY, DROPEFFECT_NONE, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL}; use winapi::um::shellapi::DragQueryFileW; @@ -34,7 +33,7 @@ use std::mem::transmute; use std::os::windows::ffi::OsStrExt; use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle}; @@ -471,6 +470,7 @@ struct WindowState { mouse_button_counter: Cell, // Initialized late so the `Window` can hold a reference to this `WindowState` handler: RefCell>>, + _drop_target: RefCell>>, scale_policy: WindowScalePolicy, dw_style: u32, @@ -677,6 +677,7 @@ impl Window<'_> { // The Window refers to this `WindowState`, so this `handler` needs to be // initialized later handler: RefCell::new(None), + _drop_target: RefCell::new(None), scale_policy: options.scale, dw_style: flags, @@ -724,10 +725,11 @@ impl Window<'_> { None }; - let drop_target = Rc::new(DropTarget::new(window_state.clone())); + let drop_target = Rc::new(DropTarget::new(Rc::downgrade(&window_state))); + *window_state._drop_target.borrow_mut() = Some(drop_target.clone()); OleInitialize(null_mut()); - RegisterDragDrop(hwnd, Rc::into_raw(drop_target) as LPDROPTARGET); + RegisterDragDrop(hwnd, Rc::as_ptr(&drop_target) as LPDROPTARGET); SetWindowLongPtrW(hwnd, GWLP_USERDATA, Rc::into_raw(window_state) as *const _ as _); SetTimer(hwnd, WIN_FRAME_TIMER, 15, None); @@ -806,7 +808,7 @@ const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { pub struct DropTarget { base: IDropTarget, - window_state: Rc, + window_state: Weak, // These are cached since DragOver and DragLeave callbacks don't provide them, // and handling drag move events gets awkward on the client end otherwise @@ -815,7 +817,7 @@ pub struct DropTarget { } impl DropTarget { - fn new(window_state: Rc) -> Self { + fn new(window_state: Weak) -> Self { Self { base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, @@ -827,8 +829,11 @@ impl DropTarget { } fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { + let Some(window_state) = self.window_state.upgrade() else { + return; + }; + unsafe { - let window_state = &*self.window_state; let mut window = window_state.create_window(); let mut window = crate::Window::new(&mut window); @@ -848,10 +853,12 @@ impl DropTarget { } fn parse_coordinates(&mut self, pt: POINTL) { - let window_state = unsafe { &*self.window_state }; + let Some(window_state) = self.window_state.upgrade() else { + return; + }; let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position = phy_point.to_logical(&window_state.window_info.borrow()) + self.drag_position = phy_point.to_logical(&window_state.window_info.borrow()); } fn parse_drop_data(&mut self, data_object: &IDataObject) { @@ -945,7 +952,11 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); @@ -968,7 +979,11 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); @@ -997,7 +1012,11 @@ impl DropTarget { ) -> HRESULT { let drop_target = &mut *(this as *mut DropTarget); - let modifiers = drop_target.window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); drop_target.parse_coordinates(pt); drop_target.parse_drop_data(&*pDataObj); From cfa06f5b59de74ec451db79670933fed45c48fee Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sat, 10 Jun 2023 23:20:46 +0300 Subject: [PATCH 19/20] Move DropTarget to its own module --- src/win/drop_target.rs | 266 +++++++++++++++++++++++++++++++++++++ src/win/mod.rs | 1 + src/win/window.rs | 290 ++++------------------------------------- 3 files changed, 292 insertions(+), 265 deletions(-) create mode 100644 src/win/drop_target.rs diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs new file mode 100644 index 0000000..1e16284 --- /dev/null +++ b/src/win/drop_target.rs @@ -0,0 +1,266 @@ +use std::ffi::OsString; +use std::mem::transmute; +use std::os::windows::prelude::OsStringExt; +use std::ptr::null_mut; +use std::rc::{Weak, Rc}; + +use winapi::Interface; +use winapi::shared::guiddef::{REFIID, IsEqualIID}; +use winapi::shared::minwindef::{DWORD, WPARAM}; +use winapi::shared::ntdef::{HRESULT, ULONG}; +use winapi::shared::windef::POINTL; +use winapi::shared::winerror::{S_OK, E_NOINTERFACE, E_UNEXPECTED}; +use winapi::shared::wtypes::DVASPECT_CONTENT; +use winapi::um::objidl::{IDataObject, FORMATETC, TYMED_HGLOBAL, STGMEDIUM}; +use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL, DROPEFFECT_NONE}; +use winapi::um::shellapi::DragQueryFileW; +use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; +use winapi::um::winuser::CF_HDROP; + +use crate::{Point, DropData, MouseEvent, Event, EventStatus, DropEffect, PhyPoint}; + +use super::WindowState; + +// These function pointers have to be stored in a (const) variable before they can be transmuted +const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter; +const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over; +const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop; +const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { + parent: IUnknownVtbl { + QueryInterface: DropTarget::query_interface, + AddRef: DropTarget::add_ref, + Release: DropTarget::release, + }, + DragEnter: unsafe { transmute(DRAG_ENTER_PTR) }, + DragOver: unsafe { transmute(DRAG_OVER_PTR) }, + DragLeave: DropTarget::drag_leave, + Drop: unsafe { transmute(DROP_PTR) }, +}; + +#[repr(C)] +pub(super) struct DropTarget { + base: IDropTarget, + + window_state: Weak, + + // These are cached since DragOver and DragLeave callbacks don't provide them, + // and handling drag move events gets awkward on the client end otherwise + drag_position: Point, + drop_data: DropData, +} + +impl DropTarget { + pub(super) fn new(window_state: Weak) -> Self { + Self { + base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, + + window_state, + + drag_position: Point::new(0.0, 0.0), + drop_data: DropData::None, + } + } + + fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { + let Some(window_state) = self.window_state.upgrade() else { + return; + }; + + unsafe { + let mut window = window_state.create_window(); + let mut window = crate::Window::new(&mut window); + + let event = Event::Mouse(event); + let event_status = window_state.handler_mut().as_mut().unwrap().on_event(&mut window, event); + + if let Some(pdwEffect) = pdwEffect { + match event_status { + EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, + EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, + EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, + EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, + _ => *pdwEffect = DROPEFFECT_NONE, + } + } + } + } + + fn parse_coordinates(&mut self, pt: POINTL) { + let Some(window_state) = self.window_state.upgrade() else { + return; + }; + + let phy_point = PhyPoint::new(pt.x, pt.y); + self.drag_position = phy_point.to_logical(&window_state.window_info()); + } + + fn parse_drop_data(&mut self, data_object: &IDataObject) { + let format = FORMATETC { + cfFormat: CF_HDROP as u16, + ptd: null_mut(), + dwAspect: DVASPECT_CONTENT, + lindex: -1, + tymed: TYMED_HGLOBAL, + }; + + let mut medium = STGMEDIUM { + tymed: 0, + u: null_mut(), + pUnkForRelease: null_mut(), + }; + + unsafe { + let hresult = data_object.GetData(&format, &mut medium); + if hresult != S_OK { + self.drop_data = DropData::None; + return; + } + + let hdrop = transmute((*medium.u).hGlobal()); + + let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0); + if item_count == 0 { + self.drop_data = DropData::None; + return; + } + + let mut paths = Vec::with_capacity(item_count as usize); + + for i in 0..item_count { + let characters = DragQueryFileW(hdrop, i, null_mut(), 0); + let buffer_size = characters as usize + 1; + let mut buffer = Vec::::with_capacity(buffer_size); + + DragQueryFileW(hdrop, i, transmute(buffer.spare_capacity_mut().as_mut_ptr()), buffer_size as u32); + buffer.set_len(buffer_size); + + paths.push(OsString::from_wide(&buffer[..characters as usize]).into()) + } + + self.drop_data = DropData::Files(paths); + } + } + + unsafe extern "system" fn query_interface( + this: *mut IUnknown, + riid: REFIID, + ppvObject: *mut *mut winapi::ctypes::c_void, + ) -> HRESULT + { + if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){ + Self::add_ref(this); + *ppvObject = this as *mut winapi::ctypes::c_void; + return S_OK; + } + + return E_NOINTERFACE; + } + + unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG { + let arc = Rc::from_raw(this); + let result = Rc::strong_count(&arc) + 1; + let _ = Rc::into_raw(arc); + + Rc::increment_strong_count(this); + + result as ULONG + } + + unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG { + let arc = Rc::from_raw(this); + let result = Rc::strong_count(&arc) - 1; + let _ = Rc::into_raw(arc); + + Rc::decrement_strong_count(this); + + result as ULONG + } + + unsafe extern "system" fn drag_enter( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + grfKeyState: DWORD, + pt: POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &mut *(this as *mut DropTarget); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + + drop_target.parse_coordinates(pt); + drop_target.parse_drop_data(&*pDataObj); + + let event = MouseEvent::DragEntered { + position: drop_target.drag_position, + modifiers, + data: drop_target.drop_data.clone(), + }; + + drop_target.on_event(Some(pdwEffect), event); + S_OK + } + + unsafe extern "system" fn drag_over( + this: *mut IDropTarget, + grfKeyState: DWORD, + pt: POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &mut *(this as *mut DropTarget); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + + drop_target.parse_coordinates(pt); + + let event = MouseEvent::DragMoved { + position: drop_target.drag_position, + modifiers, + data: drop_target.drop_data.clone(), + }; + + drop_target.on_event(Some(pdwEffect), event); + S_OK + } + + unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { + let drop_target = &mut *(this as *mut DropTarget); + drop_target.on_event(None, MouseEvent::DragLeft); + S_OK + } + + unsafe extern "system" fn drop( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + grfKeyState: DWORD, + pt: POINTL, + pdwEffect: *mut DWORD, + ) -> HRESULT + { + let drop_target = &mut *(this as *mut DropTarget); + let Some(window_state) = drop_target.window_state.upgrade() else { + return E_UNEXPECTED; + }; + + let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); + + drop_target.parse_coordinates(pt); + drop_target.parse_drop_data(&*pDataObj); + + let event = MouseEvent::DragDropped { + position: drop_target.drag_position, + modifiers, + data: drop_target.drop_data.clone(), + }; + + drop_target.on_event(Some(pdwEffect), event); + S_OK + } +} diff --git a/src/win/mod.rs b/src/win/mod.rs index 1d76d14..f66c2bd 100644 --- a/src/win/mod.rs +++ b/src/win/mod.rs @@ -1,3 +1,4 @@ +mod drop_target; mod keyboard; mod window; diff --git a/src/win/window.rs b/src/win/window.rs index cd73357..2c68b99 100644 --- a/src/win/window.rs +++ b/src/win/window.rs @@ -1,16 +1,9 @@ -use winapi::Interface; -use winapi::shared::guiddef::{GUID, REFIID, IsEqualIID}; -use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM, DWORD}; -use winapi::shared::ntdef::{HRESULT, ULONG}; -use winapi::shared::windef::{HWND, RECT, POINTL}; -use winapi::shared::winerror::{S_OK, E_NOINTERFACE, E_UNEXPECTED}; -use winapi::shared::wtypes::DVASPECT_CONTENT; +use winapi::shared::guiddef::GUID; +use winapi::shared::minwindef::{ATOM, FALSE, LPARAM, LRESULT, UINT, WPARAM}; +use winapi::shared::windef::{HWND, RECT}; use winapi::um::combaseapi::CoCreateGuid; -use winapi::um::objidl::{IDataObject, STGMEDIUM, FORMATETC, TYMED_HGLOBAL}; use winapi::um::ole2::{RegisterDragDrop, OleInitialize, RevokeDragDrop}; -use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl, LPDROPTARGET, DROPEFFECT_COPY, DROPEFFECT_NONE, DROPEFFECT_MOVE, DROPEFFECT_LINK, DROPEFFECT_SCROLL}; -use winapi::um::shellapi::DragQueryFileW; -use winapi::um::unknwnbase::{IUnknownVtbl, IUnknown}; +use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winuser::{ AdjustWindowRectEx, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetDpiForWindow, GetMessageW, GetWindowLongPtrW, LoadCursorW, PostMessageW, RegisterClassW, @@ -22,18 +15,16 @@ use winapi::um::winuser::{ WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SHOWWINDOW, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TIMER, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_CAPTION, WS_CHILD, WS_CLIPSIBLINGS, WS_MAXIMIZEBOX, WS_MINIMIZEBOX, WS_POPUPWINDOW, WS_SIZEBOX, WS_VISIBLE, - XBUTTON1, XBUTTON2, CF_HDROP, + XBUTTON1, XBUTTON2, }; -use std::cell::{Cell, RefCell}; +use std::cell::{Cell, RefCell, Ref, RefMut}; use std::collections::VecDeque; -use std::ffi::{c_void, OsStr, OsString}; +use std::ffi::{c_void, OsStr}; use std::marker::PhantomData; -use std::mem::transmute; use std::os::windows::ffi::OsStrExt; -use std::os::windows::prelude::OsStringExt; use std::ptr::null_mut; -use std::rc::{Rc, Weak}; +use std::rc::Rc; use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, Win32Handle}; @@ -41,9 +32,10 @@ const BV_WINDOW_MUST_CLOSE: UINT = WM_USER + 1; use crate::{ Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, Size, WindowEvent, - WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, DropEffect, EventStatus, DropData, Point, + WindowHandler, WindowInfo, WindowOpenOptions, WindowScalePolicy, }; +use super::drop_target::DropTarget; use super::keyboard::KeyboardState; #[cfg(feature = "opengl")] @@ -458,7 +450,7 @@ unsafe fn unregister_wnd_class(wnd_class: ATOM) { /// because of the Windows message loops' reentrant nature. Care still needs to be taken to prevent /// `handler` from indirectly triggering other events that would also need to be handled using /// `handler`. -struct WindowState { +pub(super) struct WindowState { /// The HWND belonging to this window. The window's actual state is stored in the `WindowState` /// struct associated with this HWND through `unsafe { GetWindowLongPtrW(self.hwnd, /// GWLP_USERDATA) } as *const WindowState`. @@ -486,10 +478,22 @@ struct WindowState { } impl WindowState { - fn create_window(&self) -> Window { + pub(super) fn create_window(&self) -> Window { Window { state: self } } + pub(super) fn window_info(&self) -> Ref { + self.window_info.borrow() + } + + pub(super) fn keyboard_state(&self) -> Ref { + self.keyboard_state.borrow() + } + + pub(super) fn handler_mut(&self) -> RefMut>> { + self.handler.borrow_mut() + } + /// Handle a deferred task as described in [`Self::deferred_tasks pub(self) fn handle_deferred_task(&self, task: WindowTask) { match task { @@ -530,7 +534,7 @@ impl WindowState { /// Tasks that must be deferred until the end of [`wnd_proc()`] to avoid reentrant `WindowState` /// borrows. See the docstring on [`WindowState::deferred_tasks`] for more information. #[derive(Debug, Clone)] -enum WindowTask { +pub(super) enum WindowTask { /// Resize the window to the given size. The size is in logical pixels. DPI scaling is applied /// automatically. Resize(Size), @@ -787,247 +791,3 @@ unsafe impl HasRawWindowHandle for Window<'_> { pub fn copy_to_clipboard(data: &str) { todo!() } - -// These function pointers have to be stored in a (const) variable before they can be transmuted -const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter; -const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over; -const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop; -const DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { - parent: IUnknownVtbl { - QueryInterface: DropTarget::query_interface, - AddRef: DropTarget::add_ref, - Release: DropTarget::release, - }, - DragEnter: unsafe { transmute(DRAG_ENTER_PTR) }, - DragOver: unsafe { transmute(DRAG_OVER_PTR) }, - DragLeave: DropTarget::drag_leave, - Drop: unsafe { transmute(DROP_PTR) }, -}; - -#[repr(C)] -pub struct DropTarget { - base: IDropTarget, - - window_state: Weak, - - // These are cached since DragOver and DragLeave callbacks don't provide them, - // and handling drag move events gets awkward on the client end otherwise - drag_position: Point, - drop_data: DropData, -} - -impl DropTarget { - fn new(window_state: Weak) -> Self { - Self { - base: IDropTarget { lpVtbl: &DROP_TARGET_VTBL }, - - window_state, - - drag_position: Point::new(0.0, 0.0), - drop_data: DropData::None, - } - } - - fn on_event(&self, pdwEffect: Option<*mut DWORD>, event: MouseEvent) { - let Some(window_state) = self.window_state.upgrade() else { - return; - }; - - unsafe { - let mut window = window_state.create_window(); - let mut window = crate::Window::new(&mut window); - - let event = Event::Mouse(event); - let event_status = window_state.handler.borrow_mut().as_mut().unwrap().on_event(&mut window, event); - - if let Some(pdwEffect) = pdwEffect { - match event_status { - EventStatus::AcceptDrop(DropEffect::Copy) => *pdwEffect = DROPEFFECT_COPY, - EventStatus::AcceptDrop(DropEffect::Move) => *pdwEffect = DROPEFFECT_MOVE, - EventStatus::AcceptDrop(DropEffect::Link) => *pdwEffect = DROPEFFECT_LINK, - EventStatus::AcceptDrop(DropEffect::Scroll) => *pdwEffect = DROPEFFECT_SCROLL, - _ => *pdwEffect = DROPEFFECT_NONE, - } - } - } - } - - fn parse_coordinates(&mut self, pt: POINTL) { - let Some(window_state) = self.window_state.upgrade() else { - return; - }; - - let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position = phy_point.to_logical(&window_state.window_info.borrow()); - } - - fn parse_drop_data(&mut self, data_object: &IDataObject) { - let format = FORMATETC { - cfFormat: CF_HDROP as u16, - ptd: null_mut(), - dwAspect: DVASPECT_CONTENT, - lindex: -1, - tymed: TYMED_HGLOBAL, - }; - - let mut medium = STGMEDIUM { - tymed: 0, - u: null_mut(), - pUnkForRelease: null_mut(), - }; - - unsafe { - let hresult = data_object.GetData(&format, &mut medium); - if hresult != S_OK { - self.drop_data = DropData::None; - return; - } - - let hdrop = transmute((*medium.u).hGlobal()); - - let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, null_mut(), 0); - if item_count == 0 { - self.drop_data = DropData::None; - return; - } - - let mut paths = Vec::with_capacity(item_count as usize); - - for i in 0..item_count { - let characters = DragQueryFileW(hdrop, i, null_mut(), 0); - let buffer_size = characters as usize + 1; - let mut buffer = Vec::::with_capacity(buffer_size); - - DragQueryFileW(hdrop, i, transmute(buffer.spare_capacity_mut().as_mut_ptr()), buffer_size as u32); - buffer.set_len(buffer_size); - - paths.push(OsString::from_wide(&buffer[..characters as usize]).into()) - } - - self.drop_data = DropData::Files(paths); - } - } - - unsafe extern "system" fn query_interface( - this: *mut IUnknown, - riid: REFIID, - ppvObject: *mut *mut winapi::ctypes::c_void, - ) -> HRESULT - { - if IsEqualIID(&*riid, &IUnknown::uuidof()) || IsEqualIID(&*riid, &IDropTarget::uuidof()){ - Self::add_ref(this); - *ppvObject = this as *mut winapi::ctypes::c_void; - return S_OK; - } - - return E_NOINTERFACE; - } - - unsafe extern "system" fn add_ref(this: *mut IUnknown) -> ULONG { - let arc = Rc::from_raw(this); - let result = Rc::strong_count(&arc) + 1; - let _ = Rc::into_raw(arc); - - Rc::increment_strong_count(this); - - result as ULONG - } - - unsafe extern "system" fn release(this: *mut IUnknown) -> ULONG { - let arc = Rc::from_raw(this); - let result = Rc::strong_count(&arc) - 1; - let _ = Rc::into_raw(arc); - - Rc::decrement_strong_count(this); - - result as ULONG - } - - unsafe extern "system" fn drag_enter( - this: *mut IDropTarget, - pDataObj: *const IDataObject, - grfKeyState: DWORD, - pt: POINTL, - pdwEffect: *mut DWORD, - ) -> HRESULT - { - let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { - return E_UNEXPECTED; - }; - - let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); - - drop_target.parse_coordinates(pt); - drop_target.parse_drop_data(&*pDataObj); - - let event = MouseEvent::DragEntered { - position: drop_target.drag_position, - modifiers, - data: drop_target.drop_data.clone(), - }; - - drop_target.on_event(Some(pdwEffect), event); - S_OK - } - - unsafe extern "system" fn drag_over( - this: *mut IDropTarget, - grfKeyState: DWORD, - pt: POINTL, - pdwEffect: *mut DWORD, - ) -> HRESULT - { - let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { - return E_UNEXPECTED; - }; - - let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); - - drop_target.parse_coordinates(pt); - - let event = MouseEvent::DragMoved { - position: drop_target.drag_position, - modifiers, - data: drop_target.drop_data.clone(), - }; - - drop_target.on_event(Some(pdwEffect), event); - S_OK - } - - unsafe extern "system" fn drag_leave(this: *mut IDropTarget) -> HRESULT { - let drop_target = &mut *(this as *mut DropTarget); - drop_target.on_event(None, MouseEvent::DragLeft); - S_OK - } - - unsafe extern "system" fn drop( - this: *mut IDropTarget, - pDataObj: *const IDataObject, - grfKeyState: DWORD, - pt: POINTL, - pdwEffect: *mut DWORD, - ) -> HRESULT - { - let drop_target = &mut *(this as *mut DropTarget); - let Some(window_state) = drop_target.window_state.upgrade() else { - return E_UNEXPECTED; - }; - - let modifiers = window_state.keyboard_state.borrow().get_modifiers_from_mouse_wparam(grfKeyState as WPARAM); - - drop_target.parse_coordinates(pt); - drop_target.parse_drop_data(&*pDataObj); - - let event = MouseEvent::DragDropped { - position: drop_target.drag_position, - modifiers, - data: drop_target.drop_data.clone(), - }; - - drop_target.on_event(Some(pdwEffect), event); - S_OK - } -} From 9887737d8d3364516fa48123516f774db9c3639a Mon Sep 17 00:00:00 2001 From: Jussi Viiri Date: Sun, 18 Jun 2023 20:40:17 +0300 Subject: [PATCH 20/20] Add comment about why we need to transmute IDropTargetVtbl members --- src/win/drop_target.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/win/drop_target.rs b/src/win/drop_target.rs index 1e16284..bcfd036 100644 --- a/src/win/drop_target.rs +++ b/src/win/drop_target.rs @@ -22,6 +22,8 @@ use crate::{Point, DropData, MouseEvent, Event, EventStatus, DropEffect, PhyPoin use super::WindowState; // These function pointers have to be stored in a (const) variable before they can be transmuted +// Transmuting is needed because winapi has a bug where the pt parameter has an incorrect +// type `*const POINTL` const DRAG_ENTER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_enter; const DRAG_OVER_PTR: unsafe extern "system" fn(this: *mut IDropTarget, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drag_over; const DROP_PTR: unsafe extern "system" fn(this: *mut IDropTarget, pDataObj: *const IDataObject, grfKeyState: DWORD, pt: POINTL, pdwEffect: *mut DWORD) -> HRESULT = DropTarget::drop;