use crate::webview::FileDropEvent;
use std::{
cell::UnsafeCell,
ffi::OsString,
os::{raw::c_void, windows::ffi::OsStringExt},
path::PathBuf,
ptr,
rc::Rc,
};
use windows::Win32::{
Foundation::{self as win32f, BOOL, DRAGDROP_E_INVALIDHWND, HWND, LPARAM, POINTL},
System::{
Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL},
Ole::{
IDropTarget, IDropTarget_Impl, RegisterDragDrop, RevokeDragDrop, DROPEFFECT_COPY,
DROPEFFECT_NONE,
},
SystemServices::CF_HDROP,
},
UI::{
Shell::{DragFinish, DragQueryFileW, HDROP},
WindowsAndMessaging::EnumChildWindows,
},
};
use windows_implement::implement;
use crate::application::window::Window;
pub(crate) struct FileDropController {
drop_targets: Vec<IDropTarget>,
}
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 = FileDropHandler::new(window, listener).into();
if RevokeDragDrop(hwnd) != Err(DRAGDROP_E_INVALIDHWND.into())
&& RegisterDragDrop(hwnd, &file_drop_handler).is_ok()
{
self.drop_targets.push(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 = LPARAM(closure_pointer_pointer as _);
unsafe { EnumChildWindows(hwnd, Some(enumerate_callback), lparam) };
}
unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
let closure = &mut *(lparam.0 as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
closure(hwnd).into()
}
#[implement(IDropTarget)]
pub struct FileDropHandler {
window: Rc<Window>,
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
cursor_effect: UnsafeCell<u32>,
hovered_is_valid: UnsafeCell<bool>,
}
impl FileDropHandler {
pub fn new(
window: Rc<Window>,
listener: Rc<dyn Fn(&Window, FileDropEvent) -> bool>,
) -> FileDropHandler {
Self {
window,
listener,
cursor_effect: DROPEFFECT_NONE.0.into(),
hovered_is_valid: false.into(),
}
}
unsafe fn collect_paths(
data_obj: &Option<IDataObject>,
paths: &mut Vec<PathBuf>,
) -> Option<HDROP> {
let drop_format = FORMATETC {
cfFormat: CF_HDROP.0 as u16,
ptd: ptr::null_mut(),
dwAspect: DVASPECT_CONTENT.0 as u32,
lindex: -1,
tymed: TYMED_HGLOBAL.0 as u32,
};
match data_obj
.as_ref()
.expect("Received null IDataObject")
.GetData(&drop_format)
{
Ok(medium) => {
let hdrop = HDROP(medium.Anonymous.hGlobal);
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, &mut []);
for i in 0..item_count {
let character_count = DragQueryFileW(hdrop, i, &mut []) as usize;
let str_len = character_count + 1;
let mut path_buf = Vec::with_capacity(str_len);
DragQueryFileW(hdrop, i, std::mem::transmute(path_buf.spare_capacity_mut()));
path_buf.set_len(str_len);
paths.push(OsString::from_wide(&path_buf[0..character_count]).into());
}
Some(hdrop)
}
Err(error) => {
log::warn!(
"{}",
match error.code() {
win32f::DV_E_FORMATETC => {
"Error occurred while processing dropped/hovered item: item is not a file."
}
_ => "Unexpected error occurred while processing dropped/hovered item.",
}
);
None
}
}
}
}
#[allow(non_snake_case)]
impl IDropTarget_Impl for FileDropHandler {
fn DragEnter(
&self,
pDataObj: &Option<IDataObject>,
_grfKeyState: u32,
_pt: &POINTL,
pdwEffect: *mut u32,
) -> windows::core::Result<()> {
let mut paths = Vec::new();
unsafe {
let hdrop = Self::collect_paths(pDataObj, &mut paths);
let hovered_is_valid = hdrop.is_some();
let cursor_effect = if hovered_is_valid {
DROPEFFECT_COPY
} else {
DROPEFFECT_NONE
};
*pdwEffect = cursor_effect.0;
*self.hovered_is_valid.get() = hovered_is_valid;
*self.cursor_effect.get() = cursor_effect.0;
}
(self.listener)(&self.window, FileDropEvent::Hovered(paths));
Ok(())
}
fn DragOver(
&self,
_grfKeyState: u32,
_pt: &POINTL,
pdwEffect: *mut u32,
) -> windows::core::Result<()> {
unsafe { *pdwEffect = *self.cursor_effect.get() };
Ok(())
}
fn DragLeave(&self) -> windows::core::Result<()> {
if unsafe { *self.hovered_is_valid.get() } {
(self.listener)(&self.window, FileDropEvent::Cancelled);
}
Ok(())
}
fn Drop(
&self,
pDataObj: &Option<IDataObject>,
_grfKeyState: u32,
_pt: &POINTL,
_pdwEffect: *mut u32,
) -> windows::core::Result<()> {
let mut paths = Vec::new();
unsafe {
let hdrop = Self::collect_paths(pDataObj, &mut paths);
if let Some(hdrop) = hdrop {
DragFinish(hdrop);
}
}
(self.listener)(&self.window, FileDropEvent::Dropped(paths));
Ok(())
}
}