Skip to main content

winit/platform_impl/windows/
drop_handler.rs

1use std::ffi::{c_void, OsString};
2use std::os::windows::ffi::OsStringExt;
3use std::path::PathBuf;
4use std::ptr;
5use std::sync::atomic::{AtomicUsize, Ordering};
6
7use windows_sys::core::{IUnknown, GUID, HRESULT};
8use windows_sys::Win32::Foundation::{DV_E_FORMATETC, HWND, POINTL, S_OK};
9use windows_sys::Win32::System::Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL};
10use windows_sys::Win32::System::Ole::{CF_HDROP, DROPEFFECT_COPY, DROPEFFECT_NONE};
11use windows_sys::Win32::UI::Shell::{DragFinish, DragQueryFileW, HDROP};
12
13use tracing::debug;
14
15use crate::platform_impl::platform::definitions::{
16    IDataObjectVtbl, IDropTarget, IDropTargetVtbl, IUnknownVtbl,
17};
18use crate::platform_impl::platform::WindowId;
19
20use crate::event::Event;
21use crate::window::WindowId as RootWindowId;
22
23#[repr(C)]
24pub struct FileDropHandlerData {
25    pub interface: IDropTarget,
26    refcount: AtomicUsize,
27    window: HWND,
28    send_event: Box<dyn Fn(Event<()>)>,
29    cursor_effect: u32,
30    hovered_is_valid: bool, /* If the currently hovered item is not valid there must not be any
31                             * `HoveredFileCancelled` emitted */
32}
33
34pub struct FileDropHandler {
35    pub data: *mut FileDropHandlerData,
36}
37
38#[allow(non_snake_case)]
39impl FileDropHandler {
40    pub fn new(window: HWND, send_event: Box<dyn Fn(Event<()>)>) -> FileDropHandler {
41        let data = Box::new(FileDropHandlerData {
42            interface: IDropTarget { lpVtbl: &DROP_TARGET_VTBL as *const IDropTargetVtbl },
43            refcount: AtomicUsize::new(1),
44            window,
45            send_event,
46            cursor_effect: DROPEFFECT_NONE,
47            hovered_is_valid: false,
48        });
49        FileDropHandler { data: Box::into_raw(data) }
50    }
51
52    // Implement IUnknown
53    pub unsafe extern "system" fn QueryInterface(
54        _this: *mut IUnknown,
55        _riid: *const GUID,
56        _ppvObject: *mut *mut c_void,
57    ) -> HRESULT {
58        // This function doesn't appear to be required for an `IDropTarget`.
59        // An implementation would be nice however.
60        unimplemented!();
61    }
62
63    pub unsafe extern "system" fn AddRef(this: *mut IUnknown) -> u32 {
64        let drop_handler_data = unsafe { Self::from_interface(this) };
65        let count = drop_handler_data.refcount.fetch_add(1, Ordering::Release) + 1;
66        count as u32
67    }
68
69    pub unsafe extern "system" fn Release(this: *mut IUnknown) -> u32 {
70        let drop_handler = unsafe { Self::from_interface(this) };
71        let count = drop_handler.refcount.fetch_sub(1, Ordering::Release) - 1;
72        if count == 0 {
73            // Destroy the underlying data
74            drop(unsafe { Box::from_raw(drop_handler as *mut FileDropHandlerData) });
75        }
76        count as u32
77    }
78
79    pub unsafe extern "system" fn DragEnter(
80        this: *mut IDropTarget,
81        pDataObj: *const IDataObject,
82        _grfKeyState: u32,
83        _pt: *const POINTL,
84        pdwEffect: *mut u32,
85    ) -> HRESULT {
86        use crate::event::WindowEvent::HoveredFile;
87        let drop_handler = unsafe { Self::from_interface(this) };
88        let hdrop = unsafe {
89            Self::iterate_filenames(pDataObj, |filename| {
90                drop_handler.send_event(Event::WindowEvent {
91                    window_id: RootWindowId(WindowId(drop_handler.window)),
92                    event: HoveredFile(filename),
93                });
94            })
95        };
96        drop_handler.hovered_is_valid = hdrop.is_some();
97        drop_handler.cursor_effect =
98            if drop_handler.hovered_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE };
99        unsafe {
100            *pdwEffect = drop_handler.cursor_effect;
101        }
102
103        S_OK
104    }
105
106    pub unsafe extern "system" fn DragOver(
107        this: *mut IDropTarget,
108        _grfKeyState: u32,
109        _pt: *const POINTL,
110        pdwEffect: *mut u32,
111    ) -> HRESULT {
112        let drop_handler = unsafe { Self::from_interface(this) };
113        unsafe {
114            *pdwEffect = drop_handler.cursor_effect;
115        }
116
117        S_OK
118    }
119
120    pub unsafe extern "system" fn DragLeave(this: *mut IDropTarget) -> HRESULT {
121        use crate::event::WindowEvent::HoveredFileCancelled;
122        let drop_handler = unsafe { Self::from_interface(this) };
123        if drop_handler.hovered_is_valid {
124            drop_handler.send_event(Event::WindowEvent {
125                window_id: RootWindowId(WindowId(drop_handler.window)),
126                event: HoveredFileCancelled,
127            });
128        }
129
130        S_OK
131    }
132
133    pub unsafe extern "system" fn Drop(
134        this: *mut IDropTarget,
135        pDataObj: *const IDataObject,
136        _grfKeyState: u32,
137        _pt: *const POINTL,
138        _pdwEffect: *mut u32,
139    ) -> HRESULT {
140        use crate::event::WindowEvent::DroppedFile;
141        let drop_handler = unsafe { Self::from_interface(this) };
142        let hdrop = unsafe {
143            Self::iterate_filenames(pDataObj, |filename| {
144                drop_handler.send_event(Event::WindowEvent {
145                    window_id: RootWindowId(WindowId(drop_handler.window)),
146                    event: DroppedFile(filename),
147                });
148            })
149        };
150        if let Some(hdrop) = hdrop {
151            unsafe { DragFinish(hdrop) };
152        }
153
154        S_OK
155    }
156
157    unsafe fn from_interface<'a, InterfaceT>(this: *mut InterfaceT) -> &'a mut FileDropHandlerData {
158        unsafe { &mut *(this as *mut _) }
159    }
160
161    unsafe fn iterate_filenames<F>(data_obj: *const IDataObject, callback: F) -> Option<HDROP>
162    where
163        F: Fn(PathBuf),
164    {
165        let drop_format = FORMATETC {
166            cfFormat: CF_HDROP,
167            ptd: ptr::null_mut(),
168            dwAspect: DVASPECT_CONTENT,
169            lindex: -1,
170            tymed: TYMED_HGLOBAL as u32,
171        };
172
173        let mut medium = unsafe { std::mem::zeroed() };
174        let get_data_fn = unsafe { (*(*data_obj).cast::<IDataObjectVtbl>()).GetData };
175        let get_data_result = unsafe { get_data_fn(data_obj as *mut _, &drop_format, &mut medium) };
176        if get_data_result >= 0 {
177            let hdrop = unsafe { medium.u.hGlobal as HDROP };
178
179            // The second parameter (0xFFFFFFFF) instructs the function to return the item count
180            let item_count = unsafe { DragQueryFileW(hdrop, 0xffffffff, ptr::null_mut(), 0) };
181
182            for i in 0..item_count {
183                // Get the length of the path string NOT including the terminating null character.
184                // Previously, this was using a fixed size array of MAX_PATH length, but the
185                // Windows API allows longer paths under certain circumstances.
186                let character_count =
187                    unsafe { DragQueryFileW(hdrop, i, ptr::null_mut(), 0) as usize };
188                let str_len = character_count + 1;
189
190                // Fill path_buf with the null-terminated file name
191                let mut path_buf = Vec::with_capacity(str_len);
192                unsafe {
193                    DragQueryFileW(hdrop, i, path_buf.as_mut_ptr(), str_len as u32);
194                    path_buf.set_len(str_len);
195                }
196
197                callback(OsString::from_wide(&path_buf[0..character_count]).into());
198            }
199
200            Some(hdrop)
201        } else if get_data_result == DV_E_FORMATETC {
202            // If the dropped item is not a file this error will occur.
203            // In this case it is OK to return without taking further action.
204            debug!("Error occurred while processing dropped/hovered item: item is not a file.");
205            None
206        } else {
207            debug!("Unexpected error occurred while processing dropped/hovered item.");
208            None
209        }
210    }
211}
212
213impl FileDropHandlerData {
214    fn send_event(&self, event: Event<()>) {
215        (self.send_event)(event);
216    }
217}
218
219impl Drop for FileDropHandler {
220    fn drop(&mut self) {
221        unsafe {
222            FileDropHandler::Release(self.data as *mut IUnknown);
223        }
224    }
225}
226
227static DROP_TARGET_VTBL: IDropTargetVtbl = IDropTargetVtbl {
228    parent: IUnknownVtbl {
229        QueryInterface: FileDropHandler::QueryInterface,
230        AddRef: FileDropHandler::AddRef,
231        Release: FileDropHandler::Release,
232    },
233    DragEnter: FileDropHandler::DragEnter,
234    DragOver: FileDropHandler::DragOver,
235    DragLeave: FileDropHandler::DragLeave,
236    Drop: FileDropHandler::Drop,
237};