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 - } -}