diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a508098..4a101978 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - Added `WindowBuilderExt::with_gtk_theme_variant` to X11-specific `WindowBuilder` functions. - Fixed UTF8 handling bug in X11 `set_title` function. - On Windows, `Window::set_cursor` now applies immediately instead of requiring specific events to occur first. +- On Windows, the `HoveredFile` and `HoveredFileCancelled` events are now implemented. - On Windows, fix `Window::set_maximized`. - On Windows 10, fix transparency (#260). - On macOS, fix modifiers during key repeat. diff --git a/Cargo.toml b/Cargo.toml index 995ba03f..4a7209ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,15 +39,19 @@ version = "0.3.6" features = [ "combaseapi", "dwmapi", + "errhandlingapi", "hidusage", "libloaderapi", "objbase", + "ole2", "processthreadsapi", "shellapi", "shellscalingapi", "shobjidl_core", "unknwnbase", + "winbase", "windowsx", + "winerror", "wingdi", "winnt", "winuser", diff --git a/src/platform/windows/drop_handler.rs b/src/platform/windows/drop_handler.rs new file mode 100644 index 00000000..e7bd12d0 --- /dev/null +++ b/src/platform/windows/drop_handler.rs @@ -0,0 +1,203 @@ +use std::ffi::OsString; +use std::os::windows::ffi::OsStringExt; +use std::path::PathBuf; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::{mem, ptr}; + +use winapi::ctypes::c_void; +use winapi::shared::guiddef::REFIID; +use winapi::shared::minwindef::{DWORD, MAX_PATH, UINT, ULONG}; +use winapi::shared::windef::{HWND, POINTL}; +use winapi::shared::winerror::S_OK; +use winapi::um::objidl::IDataObject; +use winapi::um::oleidl::{IDropTarget, IDropTargetVtbl}; +use winapi::um::winnt::HRESULT; +use winapi::um::{shellapi, unknwnbase}; + +use platform::platform::events_loop::send_event; +use platform::platform::WindowId; + +use {Event, WindowId as SuperWindowId}; + +#[repr(C)] +pub struct FileDropHandlerData { + pub interface: IDropTarget, + refcount: AtomicUsize, + window: HWND, +} + +pub struct FileDropHandler { + pub data: *mut FileDropHandlerData, +} + +#[allow(non_snake_case)] +impl FileDropHandler { + pub fn new(window: HWND) -> FileDropHandler { + let data = Box::new(FileDropHandlerData { + interface: IDropTarget { + lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl, + }, + refcount: AtomicUsize::new(1), + window, + }); + FileDropHandler { + data: Box::into_raw(data), + } + } + + // Implement IUnknown + pub unsafe extern "system" fn QueryInterface( + _this: *mut unknwnbase::IUnknown, + _riid: REFIID, + _ppvObject: *mut *mut c_void, + ) -> HRESULT { + // This function doesn't appear to be required for an `IDropTarget`. + // An implementation would be nice however. + unimplemented!(); + } + + pub unsafe extern "system" fn AddRef(this: *mut unknwnbase::IUnknown) -> ULONG { + let drop_handler_data = Self::from_interface(this); + let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1; + count as ULONG + } + + pub unsafe extern "system" fn Release(this: *mut unknwnbase::IUnknown) -> ULONG { + let drop_handler = Self::from_interface(this); + let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1; + if count == 0 { + // Destroy the underlying data + Box::from_raw(drop_handler as *mut FileDropHandlerData); + } + count as ULONG + } + + pub unsafe extern "system" fn DragEnter( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + _grfKeyState: DWORD, + _pt: *const POINTL, + _pdwEffect: *mut DWORD, + ) -> HRESULT { + use events::WindowEvent::HoveredFile; + let drop_handler = Self::from_interface(this); + Self::iterate_filenames(pDataObj, |filename| { + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(drop_handler.window)), + event: HoveredFile(filename), + }); + }); + + S_OK + } + + pub unsafe extern "system" fn DragOver( + _this: *mut IDropTarget, + _grfKeyState: DWORD, + _pt: *const POINTL, + _pdwEffect: *mut DWORD, + ) -> HRESULT { + S_OK + } + + pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT { + use events::WindowEvent::HoveredFileCancelled; + let drop_handler = Self::from_interface(this); + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(drop_handler.window)), + event: HoveredFileCancelled, + }); + + S_OK + } + + pub unsafe extern "system" fn Drop( + this: *mut IDropTarget, + pDataObj: *const IDataObject, + _grfKeyState: DWORD, + _pt: *const POINTL, + _pdwEffect: *mut DWORD, + ) -> HRESULT { + use events::WindowEvent::DroppedFile; + let drop_handler = Self::from_interface(this); + let hdrop = Self::iterate_filenames(pDataObj, |filename| { + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(drop_handler.window)), + event: DroppedFile(filename), + }); + }); + shellapi::DragFinish(hdrop); + + S_OK + } + + unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData { + &mut *(this as *mut _) + } + + unsafe fn iterate_filenames(data_obj: *const IDataObject, callback: F) -> shellapi::HDROP + where + F: Fn(PathBuf), + { + use winapi::ctypes::wchar_t; + use winapi::shared::winerror::SUCCEEDED; + use winapi::shared::wtypes::{CLIPFORMAT, DVASPECT_CONTENT}; + use winapi::um::objidl::{FORMATETC, TYMED_HGLOBAL}; + use winapi::um::shellapi::DragQueryFileW; + use winapi::um::winuser::CF_HDROP; + + let mut drop_format = FORMATETC { + cfFormat: CF_HDROP as CLIPFORMAT, + ptd: ptr::null(), + dwAspect: DVASPECT_CONTENT, + lindex: -1, + tymed: TYMED_HGLOBAL, + }; + + let mut medium = mem::uninitialized(); + if SUCCEEDED((*data_obj).GetData(&mut drop_format, &mut medium)) { + let hglobal = (*medium.u).hGlobal(); + let hdrop = (*hglobal) as shellapi::HDROP; + + // The second parameter (0xFFFFFFFF) instructs the function to return the item count + let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0); + + let mut pathbuf: [wchar_t; MAX_PATH] = mem::uninitialized(); + + for i in 0..item_count { + let character_count = + DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), MAX_PATH as UINT) as usize; + + if character_count > 0 { + callback(OsString::from_wide(&pathbuf[0..character_count]).into()); + } + } + + return hdrop; + } + + // The call to `GetData` must succeed and the file handle must be returned before this + // point + unreachable!(); + } +} + +impl Drop for FileDropHandler { + fn drop(&mut self) { + unsafe { + FileDropHandler::Release(self.data as *mut unknwnbase::IUnknown); + } + } +} + +static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl { + parent: unknwnbase::IUnknownVtbl { + QueryInterface: FileDropHandler::QueryInterface, + AddRef: FileDropHandler::AddRef, + Release: FileDropHandler::Release, + }, + DragEnter: FileDropHandler::DragEnter, + DragOver: FileDropHandler::DragOver, + DragLeave: FileDropHandler::DragLeave, + Drop: FileDropHandler::Drop, +}; diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index b5ec1a5a..b14faa10 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -15,8 +15,6 @@ use std::{mem, ptr, thread}; use std::cell::RefCell; use std::collections::HashMap; -use std::ffi::OsString; -use std::os::windows::ffi::OsStringExt; use std::os::windows::io::AsRawHandle; use std::sync::{Arc, Barrier, mpsc, Mutex}; @@ -29,13 +27,14 @@ use winapi::shared::minwindef::{ LOWORD, LPARAM, LRESULT, - MAX_PATH, UINT, WPARAM, }; use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::shared::windowsx; -use winapi::um::{winuser, shellapi, processthreadsapi}; +use winapi::shared::winerror::S_OK; +use winapi::um::{winuser, processthreadsapi, ole2}; +use winapi::um::oleidl::LPDROPTARGET; use winapi::um::winnt::{LONG, LPCSTR, SHORT}; use { @@ -57,6 +56,7 @@ use platform::platform::dpi::{ enable_non_client_dpi_scaling, get_hwnd_scale_factor, }; +use platform::platform::drop_handler::FileDropHandler; use platform::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; use platform::platform::icon::WinIcon; use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; @@ -161,7 +161,8 @@ impl EventsLoop { *context_stash.borrow_mut() = Some(ThreadLocalData { sender: tx, windows: HashMap::with_capacity(4), - mouse_buttons_down: 0 + file_drop_handlers: HashMap::with_capacity(4), + mouse_buttons_down: 0, }); }); @@ -371,11 +372,12 @@ thread_local!(static CONTEXT_STASH: RefCell> = RefCell:: struct ThreadLocalData { sender: mpsc::Sender, windows: HashMap>>, - mouse_buttons_down: u32 + file_drop_handlers: HashMap, // Each window has its own drop handler. + mouse_buttons_down: u32, } // Utility function that dispatches an event on the current thread. -fn send_event(event: Event) { +pub fn send_event(event: Event) { CONTEXT_STASH.with(|context_stash| { let context_stash = context_stash.borrow(); @@ -423,6 +425,30 @@ pub unsafe extern "system" fn callback( lparam: LPARAM, ) -> LRESULT { match msg { + winuser::WM_CREATE => { + use winapi::shared::winerror::{OLE_E_WRONGCOMPOBJ, RPC_E_CHANGED_MODE}; + let ole_init_result = ole2::OleInitialize(ptr::null_mut()); + // It is ok if the initialize result is `S_FALSE` because it might happen that + // multiple windows are created on the same thread. + if ole_init_result == OLE_E_WRONGCOMPOBJ { + panic!("OleInitialize failed! Result was: `OLE_E_WRONGCOMPOBJ`"); + } else if ole_init_result == RPC_E_CHANGED_MODE { + panic!("OleInitialize failed! Result was: `RPC_E_CHANGED_MODE`"); + } + + CONTEXT_STASH.with(|context_stash| { + let mut context_stash = context_stash.borrow_mut(); + + let drop_handlers = &mut context_stash.as_mut().unwrap().file_drop_handlers; + let new_handler = FileDropHandler::new(window); + let handler_interface_ptr = &mut (*new_handler.data).interface as LPDROPTARGET; + drop_handlers.insert(window, new_handler); + + assert_eq!(ole2::RegisterDragDrop(window, handler_interface_ptr), S_OK); + }); + 0 + }, + winuser::WM_NCCREATE => { enable_non_client_dpi_scaling(window); winuser::DefWindowProcW(window, msg, wparam, lparam) @@ -441,7 +467,10 @@ pub unsafe extern "system" fn callback( use events::WindowEvent::Destroyed; CONTEXT_STASH.with(|context_stash| { let mut context_stash = context_stash.borrow_mut(); - context_stash.as_mut().unwrap().windows.remove(&window); + ole2::RevokeDragDrop(window); + let context_stash_mut = context_stash.as_mut().unwrap(); + context_stash_mut.file_drop_handlers.remove(&window); + context_stash_mut.windows.remove(&window); }); send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), @@ -993,24 +1022,7 @@ pub unsafe extern "system" fn callback( }, winuser::WM_DROPFILES => { - use events::WindowEvent::DroppedFile; - - let hdrop = wparam as shellapi::HDROP; - let mut pathbuf: [u16; MAX_PATH] = mem::uninitialized(); - let num_drops = shellapi::DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0); - - for i in 0..num_drops { - let nch = shellapi::DragQueryFileW(hdrop, i, pathbuf.as_mut_ptr(), - MAX_PATH as u32) as usize; - if nch > 0 { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: DroppedFile(OsString::from_wide(&pathbuf[0..nch]).into()) - }); - } - } - - shellapi::DragFinish(hdrop); + // See `FileDropHandler` for implementation. 0 }, diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index e82bd223..f7a576eb 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -49,6 +49,7 @@ unsafe impl Send for WindowId {} unsafe impl Sync for WindowId {} mod dpi; +mod drop_handler; mod event; mod events_loop; mod icon;