use crate::webview::FileDropEvent;
use std::{
ffi::OsString,
os::{raw::c_void, windows::ffi::OsStringExt},
path::PathBuf,
ptr,
rc::Rc,
sync::atomic::{AtomicUsize, Ordering},
};
use winapi::shared::windef::HWND;
use crate::application::window::Window;
pub(crate) struct FileDropController {
drop_targets: Vec<*mut IDropTarget>,
}
impl Drop for FileDropController {
fn drop(&mut self) {
unsafe {
for ptr in &self.drop_targets {
Box::from_raw(*ptr);
}
}
}
}
impl FileDropController {
pub(crate) fn new() -> Self {
FileDropController {
drop_targets: Vec::new(),
}
}
pub(crate) fn listen(
&mut self,
hwnd: HWND,
window: Rc<Window>,
handler: Box<dyn Fn(&Window, FileDropEvent) -> bool>,
) {
let listener = Rc::new(handler);
enumerate_child_windows(hwnd, |hwnd| {
self.inject(hwnd, window.clone(), listener.clone())
});
}
fn inject(
&mut self,
hwnd: HWND,
window: Rc<Window>,
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
) -> bool {
unsafe {
let file_drop_handler = IDropTarget::new(hwnd, window, listener);
let handler_interface_ptr =
&mut (*file_drop_handler.data).interface as winapi::um::oleidl::LPDROPTARGET;
if winapi::um::ole2::RevokeDragDrop(hwnd) != winapi::shared::winerror::DRAGDROP_E_INVALIDHWND
&& winapi::um::ole2::RegisterDragDrop(hwnd, handler_interface_ptr) == S_OK
{
self
.drop_targets
.push(Box::into_raw(Box::new(file_drop_handler)));
}
}
true
}
}
fn enumerate_child_windows<F>(hwnd: HWND, mut callback: F)
where
F: FnMut(HWND) -> bool,
{
let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback;
let closure_pointer_pointer: *mut c_void = unsafe { std::mem::transmute(&mut trait_obj) };
let lparam = closure_pointer_pointer as winapi::shared::minwindef::LPARAM;
unsafe { winapi::um::winuser::EnumChildWindows(hwnd, Some(enumerate_callback), lparam) };
}
unsafe extern "system" fn enumerate_callback(
hwnd: HWND,
lparam: winapi::shared::minwindef::LPARAM,
) -> winapi::shared::minwindef::BOOL {
let closure = &mut *(lparam as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
if closure(hwnd) {
winapi::shared::minwindef::TRUE
} else {
winapi::shared::minwindef::FALSE
}
}
use winapi::{
shared::{
guiddef::REFIID,
minwindef::{DWORD, UINT, ULONG},
windef::POINTL,
winerror::S_OK,
},
um::{
objidl::IDataObject,
oleidl::{IDropTarget as NativeIDropTarget, IDropTargetVtbl, DROPEFFECT_COPY, DROPEFFECT_NONE},
shellapi, unknwnbase,
winnt::HRESULT,
},
};
#[allow(non_camel_case_types)]
#[repr(C)]
struct IDropTargetData {
pub interface: NativeIDropTarget,
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
refcount: AtomicUsize,
hwnd: HWND,
window: Rc<Window>,
cursor_effect: DWORD,
hovered_is_valid: bool,
}
#[allow(non_camel_case_types)]
pub struct IDropTarget {
data: *mut IDropTargetData,
}
#[allow(non_snake_case)]
impl IDropTarget {
fn new(
hwnd: HWND,
window: Rc<Window>,
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
) -> IDropTarget {
let data = Box::new(IDropTargetData {
listener,
interface: NativeIDropTarget {
lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl,
},
refcount: AtomicUsize::new(1),
hwnd,
window,
cursor_effect: DROPEFFECT_NONE,
hovered_is_valid: false,
});
IDropTarget {
data: Box::into_raw(data),
}
}
pub unsafe extern "system" fn QueryInterface(
_this: *mut unknwnbase::IUnknown,
_riid: REFIID,
_ppvObject: *mut *mut winapi::ctypes::c_void,
) -> HRESULT {
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 {
Box::from_raw(drop_handler as *mut IDropTargetData);
}
count as ULONG
}
pub unsafe extern "system" fn DragEnter(
this: *mut NativeIDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
let mut paths = Vec::new();
let drop_handler = Self::from_interface(this);
let hdrop = Self::collect_paths(pDataObj, &mut paths);
drop_handler.hovered_is_valid = hdrop.is_some();
drop_handler.cursor_effect = if drop_handler.hovered_is_valid {
DROPEFFECT_COPY
} else {
DROPEFFECT_NONE
};
*pdwEffect = drop_handler.cursor_effect;
(drop_handler.listener)(&drop_handler.window, FileDropEvent::Hovered(paths));
S_OK
}
pub unsafe extern "system" fn DragOver(
this: *mut NativeIDropTarget,
_grfKeyState: DWORD,
_pt: *const POINTL,
pdwEffect: *mut DWORD,
) -> HRESULT {
let drop_handler = Self::from_interface(this);
*pdwEffect = drop_handler.cursor_effect;
S_OK
}
pub unsafe extern "system" fn DragLeave(this: *mut NativeIDropTarget) -> HRESULT {
let drop_handler = Self::from_interface(this);
if drop_handler.hovered_is_valid {
(drop_handler.listener)(&drop_handler.window, FileDropEvent::Cancelled);
}
S_OK
}
pub unsafe extern "system" fn Drop(
this: *mut NativeIDropTarget,
pDataObj: *const IDataObject,
_grfKeyState: DWORD,
_pt: *const POINTL,
_pdwEffect: *mut DWORD,
) -> HRESULT {
let mut paths = Vec::new();
let drop_handler = Self::from_interface(this);
let hdrop = Self::collect_paths(pDataObj, &mut paths);
if let Some(hdrop) = hdrop {
shellapi::DragFinish(hdrop);
}
(drop_handler.listener)(&drop_handler.window, FileDropEvent::Dropped(paths));
S_OK
}
unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut IDropTargetData {
&mut *(this as *mut _)
}
unsafe fn collect_paths(
data_obj: *const IDataObject,
paths: &mut Vec<PathBuf>,
) -> Option<shellapi::HDROP> {
use winapi::{
shared::{
winerror::{DV_E_FORMATETC, SUCCEEDED},
wtypes::{CLIPFORMAT, DVASPECT_CONTENT},
},
um::{
objidl::{FORMATETC, TYMED_HGLOBAL},
shellapi::DragQueryFileW,
winuser::CF_HDROP,
},
};
let drop_format = FORMATETC {
cfFormat: CF_HDROP as CLIPFORMAT,
ptd: ptr::null(),
dwAspect: DVASPECT_CONTENT,
lindex: -1,
tymed: TYMED_HGLOBAL,
};
let mut medium = std::mem::zeroed();
let get_data_result = (*data_obj).GetData(&drop_format, &mut medium);
if SUCCEEDED(get_data_result) {
let hglobal = (*medium.u).hGlobal();
let hdrop = (*hglobal) as shellapi::HDROP;
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, ptr::null_mut(), 0);
for i in 0..item_count {
let character_count = DragQueryFileW(hdrop, i, ptr::null_mut(), 0) as usize;
let str_len = character_count + 1;
let mut path_buf = Vec::with_capacity(str_len);
DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as UINT);
path_buf.set_len(str_len);
paths.push(OsString::from_wide(&path_buf[0..character_count]).into());
}
Some(hdrop)
} else if get_data_result == DV_E_FORMATETC {
log::warn!("Error occured while processing dropped/hovered item: item is not a file.");
None
} else {
log::warn!("Unexpected error occured while processing dropped/hovered item.");
None
}
}
}
impl Drop for IDropTarget {
fn drop(&mut self) {
unsafe {
IDropTarget::Release(self.data as *mut unknwnbase::IUnknown);
}
}
}
static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
parent: unknwnbase::IUnknownVtbl {
QueryInterface: IDropTarget::QueryInterface,
AddRef: IDropTarget::AddRef,
Release: IDropTarget::Release,
},
DragEnter: IDropTarget::DragEnter,
DragOver: IDropTarget::DragOver,
DragLeave: IDropTarget::DragLeave,
Drop: IDropTarget::Drop,
};