diff --git a/.changes/file-drop-enhancements.md b/.changes/file-drop-enhancements.md new file mode 100644 index 000000000..dd442f464 --- /dev/null +++ b/.changes/file-drop-enhancements.md @@ -0,0 +1,10 @@ +--- +"wry": "patch" +--- + +**Breaking change**: Refactored the file-drop handling on the webview for better representation of the actual drag and drop operation: + +- Renamed `file-drop` cargo feature flag to `drag-drop`. +- Removed `FileDropEvent` enum and replaced with a new `DragDropEvent` enum. +- Renamed `WebViewAttributes::file_drop_handler` field to `WebViewAttributes::drag_drop_handler`. +- Renamed `WebViewAttributes::with_file_drop_handler` method to `WebViewAttributes::with_drag_drop_handler`. diff --git a/Cargo.toml b/Cargo.toml index fcd81912f..211ca8ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = [ "/.changes", "/.github", "/audits", "/wry-logo.svg" ] [package.metadata.docs.rs] no-default-features = true -features = [ "file-drop", "protocol", "os-webview" ] +features = [ "drag-drop", "protocol", "os-webview" ] targets = [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", @@ -25,9 +25,9 @@ rustc-args = [ "--cfg", "docsrs" ] rustdoc-args = [ "--cfg", "docsrs" ] [features] -default = [ "file-drop", "objc-exception", "protocol", "os-webview" ] +default = [ "drag-drop", "objc-exception", "protocol", "os-webview" ] objc-exception = [ "objc/exception" ] -file-drop = [ ] +drag-drop = [ ] protocol = [ ] devtools = [ ] transparent = [ ] diff --git a/examples/simple.rs b/examples/simple.rs index 1834d8b73..5528e41b9 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -34,7 +34,24 @@ fn main() -> wry::Result<()> { WebViewBuilder::new_gtk(vbox) }; - let _webview = builder.with_url("http://tauri.app").build()?; + let _webview = builder + .with_url("http://tauri.app") + .with_drag_drop_handler(|e| { + match e { + wry::DragDropEvent::Enter { paths, position } => { + println!("DragEnter: {position:?} {paths:?} ") + } + wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "), + wry::DragDropEvent::Drop { paths, position } => { + println!("DragDrop: {position:?} {paths:?} ") + } + wry::DragDropEvent::Leave => println!("DragLeave"), + _ => {} + } + + true + }) + .build()?; event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; diff --git a/src/lib.rs b/src/lib.rs index 30de69a4e..aa7c8adaa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,7 +179,7 @@ //! for the crate to work. This feature was added in preparation of other ports like cef and servo. //! - `protocol` (default): Enables [`WebViewBuilder::with_custom_protocol`] to define custom URL scheme for handling tasks like //! loading assets. -//! - `file-drop` (default): Enables [`WebViewBuilder::with_file_drop_handler`] to control the behaviour when there are files +//! - `drag-drop` (default): Enables [`WebViewBuilder::with_drag_drop_handler`] to control the behaviour when there are files //! interacting with the window. //! - `devtools`: Enables devtools on release builds. Devtools are always enabled in debug builds. //! On **macOS**, enabling devtools, requires calling private apis so you should not enable this flag in release @@ -191,10 +191,11 @@ //! libraries and prevent from building documentation on doc.rs fails. //! - `linux-body`: Enables body support of custom protocol request on Linux. Requires //! webkit2gtk v2.40 or above. -//! - `tracing`: enables [tracing] for `evaluate_script`, `ipc_handler` and `custom_protocols. +//! - `tracing`: enables [`tracing`] for `evaluate_script`, `ipc_handler` and `custom_protocols. //! //! [`tao`]: https://docs.rs/tao //! [`winit`]: https://docs.rs/winit +//! [`tracing`]: https://docs.rs/tracing #![allow(clippy::new_without_default)] #![allow(clippy::default_constructed_unit_structs)] @@ -376,17 +377,18 @@ pub struct WebViewAttributes { /// using `window.ipc.postMessage("insert_message_here")` to host Rust code. pub ipc_handler: Option)>>, - /// A handler closure to process incoming [`FileDropEvent`] of the webview. + /// A handler closure to process incoming [`DragDropEvent`] of the webview. /// /// # Blocking OS Default Behavior - /// Return `true` in the callback to block the OS' default behavior of handling a file drop. + /// Return `true` in the callback to block the OS' default behavior. /// /// Note, that if you do block this behavior, it won't be possible to drop files on `` forms. /// Also note, that it's not possible to manually set the value of a `` via JavaScript for security reasons. - #[cfg(feature = "file-drop")] - pub file_drop_handler: Option bool>>, - #[cfg(not(feature = "file-drop"))] - file_drop_handler: Option bool>>, + #[cfg(feature = "drag-drop")] + #[cfg_attr(docsrs, doc(cfg(feature = "drag-drop")))] + pub drag_drop_handler: Option bool>>, + #[cfg(not(feature = "drag-drop"))] + drag_drop_handler: Option bool>>, /// A navigation handler to decide if incoming url is allowed to navigate. /// @@ -505,7 +507,7 @@ impl Default for WebViewAttributes { initialization_scripts: vec![], custom_protocols: vec![], ipc_handler: None, - file_drop_handler: None, + drag_drop_handler: None, navigation_handler: None, download_started_handler: None, download_completed_handler: None, @@ -560,7 +562,7 @@ impl<'a> WebViewBuilder<'a> { /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. /// - **Windows**: The webview will auto-resize when the passed handle is resized. - /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually. + /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually. /// /// # Panics: /// @@ -768,19 +770,20 @@ impl<'a> WebViewBuilder<'a> { self } - /// Set a handler closure to process incoming [`FileDropEvent`] of the webview. + /// Set a handler closure to process incoming [`DragDropEvent`] of the webview. /// /// # Blocking OS Default Behavior - /// Return `true` in the callback to block the OS' default behavior of handling a file drop. + /// Return `true` in the callback to block the OS' default behavior. /// /// Note, that if you do block this behavior, it won't be possible to drop files on `` forms. /// Also note, that it's not possible to manually set the value of a `` via JavaScript for security reasons. - #[cfg(feature = "file-drop")] - pub fn with_file_drop_handler(mut self, handler: F) -> Self + #[cfg(feature = "drag-drop")] + #[cfg_attr(docsrs, doc(cfg(feature = "drag-drop")))] + pub fn with_drag_drop_handler(mut self, handler: F) -> Self where - F: Fn(FileDropEvent) -> bool + 'static, + F: Fn(DragDropEvent) -> bool + 'static, { - self.attrs.file_drop_handler = Some(Box::new(handler)); + self.attrs.drag_drop_handler = Some(Box::new(handler)); self } @@ -789,7 +792,7 @@ impl<'a> WebViewBuilder<'a> { /// /// ## Note /// - /// Data URLs are not supported, use [`html`](Self::html) option instead. + /// Data URLs are not supported, use [`html`](Self::with_html) option instead. pub fn with_url_and_headers(mut self, url: impl Into, headers: http::HeaderMap) -> Self { self.attrs.url = Some(url.into()); self.attrs.headers = Some(headers); @@ -801,7 +804,7 @@ impl<'a> WebViewBuilder<'a> { /// /// ## Note /// - /// Data URLs are not supported, use [`html`](Self::html) option instead. + /// Data URLs are not supported, use [`html`](Self::with_html) option instead. pub fn with_url(mut self, url: impl Into) -> Self { self.attrs.url = Some(url.into()); self.attrs.headers = None; @@ -1067,7 +1070,7 @@ pub trait WebViewBuilderExtWindows { /// `false`, it disables all accelerator keys that access features specific to a web browser. /// The default value is `true`. See the following link to know more details. /// - /// https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings#arebrowseracceleratorkeysenabled + /// fn with_browser_accelerator_keys(self, enabled: bool) -> Self; /// Specifies the theme of webview2. This affects things like `prefers-color-scheme`. @@ -1250,7 +1253,7 @@ impl WebView { /// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`]. /// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation. /// - **macOS / Windows**: The webview will auto-resize when the passed handle is resized. - /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually. + /// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually. /// /// # Panics: /// @@ -1410,24 +1413,31 @@ impl WebView { } } -/// An event describing the files drop on the webview. +/// An event describing drag and drop operations on the webview. #[non_exhaustive] #[derive(Debug, serde::Serialize, Clone)] -pub enum FileDropEvent { - /// The file(s) have been dragged onto the window, but have not been dropped yet. - Hovered { +pub enum DragDropEvent { + /// A drag operation has entered the webview. + Enter { + /// List of paths that are being dragged onto the webview. paths: Vec, - /// The position of the mouse cursor. + /// Position of the drag operation, relative to the webview top-left corner. + position: (i32, i32), + }, + /// A drag operation is moving over the window. + Over { + /// Position of the drag operation, relative to the webview top-left corner. position: (i32, i32), }, /// The file(s) have been dropped onto the window. - Dropped { + Drop { + /// List of paths that are being dropped onto the window. paths: Vec, - /// The position of the mouse cursor. + /// Position of the drag operation, relative to the webview top-left corner. position: (i32, i32), }, - /// The file drop was aborted. - Cancelled, + /// The drag operation has been cancelled or left the window. + Leave, } /// Get WebView/Webkit version on current platform. diff --git a/src/webkitgtk/drag_drop.rs b/src/webkitgtk/drag_drop.rs new file mode 100644 index 000000000..0aeafcb00 --- /dev/null +++ b/src/webkitgtk/drag_drop.rs @@ -0,0 +1,125 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use std::{ + cell::{Cell, UnsafeCell}, + path::PathBuf, + rc::Rc, +}; + +use gtk::{glib::GString, prelude::*}; +use webkit2gtk::WebView; + +use crate::DragDropEvent; + +struct DragDropController { + paths: UnsafeCell>>, + has_entered: Cell, + position: Cell<(i32, i32)>, + handler: Box bool>, +} + +impl DragDropController { + fn new(handler: Box bool>) -> Self { + Self { + handler, + paths: UnsafeCell::new(None), + has_entered: Cell::new(false), + position: Cell::new((0, 0)), + } + } + + fn store_paths(&self, paths: Vec) { + unsafe { *self.paths.get() = Some(paths) }; + } + + fn take_paths(&self) -> Option> { + unsafe { &mut *self.paths.get() }.take() + } + + fn store_position(&self, position: (i32, i32)) { + self.position.replace(position); + } + + fn enter(&self) { + self.has_entered.set(true); + } + + fn has_entered(&self) -> bool { + self.has_entered.get() + } + + fn leave(&self) { + self.has_entered.set(false); + } + + fn call(&self, event: DragDropEvent) -> bool { + (self.handler)(event) + } +} + +pub(crate) fn connect_drag_event(webview: &WebView, handler: Box bool>) { + let controller = Rc::new(DragDropController::new(handler)); + + { + let controller = controller.clone(); + webview.connect_drag_data_received(move |_, _, _, _, data, info, _| { + if info == 2 { + let uris = data.uris(); + let paths = uris.iter().map(path_buf_from_uri).collect::>(); + controller.enter(); + controller.call(DragDropEvent::Enter { + paths: paths.clone(), + position: controller.position.get(), + }); + controller.store_paths(paths); + } + }); + } + + { + let controller = controller.clone(); + webview.connect_drag_motion(move |_, _, x, y, _| { + if controller.has_entered() { + controller.call(DragDropEvent::Over { position: (x, y) }); + } else { + controller.store_position((x, y)); + } + false + }); + } + + { + let controller = controller.clone(); + webview.connect_drag_drop(move |_, _, x, y, _| { + if controller.has_entered() { + if let Some(paths) = controller.take_paths() { + controller.leave(); + return controller.call(DragDropEvent::Drop { + paths, + position: (x, y), + }); + } + } + + false + }); + } + + webview.connect_drag_leave(move |_, _, time| { + if time == 0 { + controller.leave(); + controller.call(DragDropEvent::Leave); + } + }); +} + +fn path_buf_from_uri(gstr: &GString) -> PathBuf { + let path = gstr.as_str(); + let path = path.strip_prefix("file://").unwrap_or(path); + let path = percent_encoding::percent_decode(path.as_bytes()) + .decode_utf8_lossy() + .to_string(); + PathBuf::from(path) +} diff --git a/src/webkitgtk/file_drop.rs b/src/webkitgtk/file_drop.rs deleted file mode 100644 index c4690de40..000000000 --- a/src/webkitgtk/file_drop.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2020-2023 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -use std::{cell::Cell, path::PathBuf, rc::Rc}; - -use gtk::{glib, prelude::*}; -use webkit2gtk::WebView; - -use crate::FileDropEvent; - -pub(crate) fn connect_drag_event(webview: &WebView, handler: Box bool>) { - let listener = Rc::new((handler, Cell::new(None))); - - let listener_ref = listener.clone(); - webview.connect_drag_data_received(move |_, _, x, y, data, info, _| { - if info == 2 { - let uris = data.uris(); - let paths = uris - .iter() - .map(|gstr| { - let path = gstr.as_str(); - let path = path.strip_prefix("file://").unwrap_or(path); - let path = percent_encoding::percent_decode(path.as_bytes()) - .decode_utf8_lossy() - .to_string(); - PathBuf::from(path) - }) - .collect::>(); - - listener_ref.1.set(Some(paths.clone())); - - listener_ref.0(FileDropEvent::Hovered { - paths, - position: (x, y), - }); - } else { - // drag_data_received is called twice, so we can ignore this signal - } - }); - - let listener_ref = listener.clone(); - webview.connect_drag_drop(move |_, _, x, y, _| { - let paths = listener_ref.1.take(); - if let Some(paths) = paths { - listener_ref.0(FileDropEvent::Dropped { - paths, - position: (x, y), - }) - } else { - false - } - }); - - let listener_ref = listener.clone(); - webview.connect_drag_leave(move |_, _, time| { - if time == 0 { - // The user cancelled the drag n drop - listener_ref.0(FileDropEvent::Cancelled); - } else { - // The user dropped the file on the window, but this will be handled in connect_drag_drop instead - } - }); - - // Called when a drag "fails" - we'll just emit a Cancelled event. - let listener_ref = listener.clone(); - webview.connect_drag_failed(move |_, _, _| { - if listener_ref.0(FileDropEvent::Cancelled) { - glib::Propagation::Stop - } else { - gtk::glib::Propagation::Proceed - } - }); -} diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index a551c4e1f..a0f343f93 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -43,7 +43,7 @@ use crate::{ use self::web_context::WebContextExt; -mod file_drop; +mod drag_drop; mod synthetic_mouse_events; mod web_context; @@ -260,9 +260,9 @@ impl InnerWebView { // IPC handler Self::attach_ipc_handler(webview.clone(), web_context, &mut attributes); - // File drop handler - if let Some(file_drop_handler) = attributes.file_drop_handler.take() { - file_drop::connect_drag_event(&webview, file_drop_handler); + // Drag drop handler + if let Some(drag_drop_handler) = attributes.drag_drop_handler.take() { + drag_drop::connect_drag_event(&webview, drag_drop_handler); } web_context.register_automation(webview.clone()); diff --git a/src/webview2/file_drop.rs b/src/webview2/drag_drop.rs similarity index 69% rename from src/webview2/file_drop.rs rename to src/webview2/drag_drop.rs index 9d98f3d34..ba6561b3c 100644 --- a/src/webview2/file_drop.rs +++ b/src/webview2/drag_drop.rs @@ -4,7 +4,7 @@ // A silly implementation of file drop handling for Windows! -use crate::FileDropEvent; +use crate::DragDropEvent; use std::{ cell::UnsafeCell, @@ -35,14 +35,14 @@ use windows::Win32::{ use windows_implement::implement; #[derive(Default)] -pub(crate) struct FileDropController { +pub(crate) struct DragDropController { drop_targets: Vec, } -impl FileDropController { +impl DragDropController { #[inline] - pub(crate) fn new(hwnd: HWND, handler: Box bool>) -> Self { - let mut controller = FileDropController::default(); + pub(crate) fn new(hwnd: HWND, handler: Box bool>) -> Self { + let mut controller = DragDropController::default(); let handler = Rc::new(handler); @@ -63,12 +63,12 @@ impl FileDropController { } #[inline] - fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc bool>) -> bool { - let file_drop_handler: IDropTarget = FileDropHandler::new(hwnd, handler).into(); + fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc bool>) -> bool { + let drag_drop_target: IDropTarget = DragDropTarget::new(hwnd, handler).into(); if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into()) - && unsafe { RegisterDragDrop(hwnd, &file_drop_handler) }.is_ok() + && unsafe { RegisterDragDrop(hwnd, &drag_drop_target) }.is_ok() { - self.drop_targets.push(file_drop_handler); + self.drop_targets.push(drag_drop_target); } true @@ -76,27 +76,27 @@ impl FileDropController { } #[implement(IDropTarget)] -pub struct FileDropHandler { +pub struct DragDropTarget { hwnd: HWND, - listener: Rc bool>, + listener: Rc bool>, cursor_effect: UnsafeCell, - hovered_is_valid: UnsafeCell, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ + enter_is_valid: UnsafeCell, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */ } -impl FileDropHandler { - pub fn new(hwnd: HWND, listener: Rc bool>) -> FileDropHandler { +impl DragDropTarget { + pub fn new(hwnd: HWND, listener: Rc bool>) -> DragDropTarget { Self { hwnd, listener, cursor_effect: DROPEFFECT_NONE.into(), - hovered_is_valid: false.into(), + enter_is_valid: false.into(), } } - unsafe fn collect_paths( - data_obj: Option<&IDataObject>, - paths: &mut Vec, - ) -> Option { + unsafe fn iterate_filenames(data_obj: Option<&IDataObject>, mut callback: F) -> Option + where + F: FnMut(PathBuf), + { let drop_format = FORMATETC { cfFormat: CF_HDROP.0, ptd: ptr::null_mut(), @@ -128,7 +128,7 @@ impl FileDropHandler { 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()); + callback(OsString::from_wide(&path_buf[0..character_count]).into()); } Some(hdrop) @@ -152,7 +152,7 @@ impl FileDropHandler { } #[allow(non_snake_case)] -impl IDropTarget_Impl for FileDropHandler { +impl IDropTarget_Impl for DragDropTarget { fn DragEnter( &self, pDataObj: Option<&IDataObject>, @@ -160,44 +160,53 @@ impl IDropTarget_Impl for FileDropHandler { pt: &POINTL, pdwEffect: *mut DROPEFFECT, ) -> windows::core::Result<()> { + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { ScreenToClient(self.hwnd, &mut pt) }; + let mut paths = Vec::new(); + let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) }; + (self.listener)(DragDropEvent::Enter { + paths, + position: (pt.x as _, pt.y as _), + }); + unsafe { - let hdrop = Self::collect_paths(pDataObj, &mut paths); - let hovered_is_valid = hdrop.is_some(); - let cursor_effect = if hovered_is_valid { + let enter_is_valid = hdrop.is_some(); + *self.enter_is_valid.get() = enter_is_valid; + + let cursor_effect = if enter_is_valid { DROPEFFECT_COPY } else { DROPEFFECT_NONE }; *pdwEffect = cursor_effect; - *self.hovered_is_valid.get() = hovered_is_valid; *self.cursor_effect.get() = cursor_effect; - - let mut pt = POINT { x: pt.x, y: pt.y }; - ScreenToClient(self.hwnd, &mut pt); } - (self.listener)(FileDropEvent::Hovered { - paths, - position: (pt.x as _, pt.y as _), - }); - Ok(()) } fn DragOver( &self, _grfKeyState: MODIFIERKEYS_FLAGS, - _pt: &POINTL, + pt: &POINTL, pdwEffect: *mut DROPEFFECT, ) -> windows::core::Result<()> { + if unsafe { *self.enter_is_valid.get() } { + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { ScreenToClient(self.hwnd, &mut pt) }; + (self.listener)(DragDropEvent::Over { + position: (pt.x as _, pt.y as _), + }); + } + unsafe { *pdwEffect = *self.cursor_effect.get() }; Ok(()) } fn DragLeave(&self) -> windows::core::Result<()> { - if unsafe { *self.hovered_is_valid.get() } { - (self.listener)(FileDropEvent::Cancelled); + if unsafe { *self.enter_is_valid.get() } { + (self.listener)(DragDropEvent::Leave); } Ok(()) } @@ -209,22 +218,22 @@ impl IDropTarget_Impl for FileDropHandler { pt: &POINTL, _pdwEffect: *mut DROPEFFECT, ) -> windows::core::Result<()> { - let mut paths = Vec::new(); - unsafe { - let hdrop = Self::collect_paths(pDataObj, &mut paths); + if unsafe { *self.enter_is_valid.get() } { + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { ScreenToClient(self.hwnd, &mut pt) }; + + let mut paths = Vec::new(); + let hdrop = unsafe { Self::iterate_filenames(pDataObj, |path| paths.push(path)) }; + (self.listener)(DragDropEvent::Drop { + paths, + position: (pt.x as _, pt.y as _), + }); + if let Some(hdrop) = hdrop { - DragFinish(hdrop); + unsafe { DragFinish(hdrop) }; } - - let mut pt = POINT { x: pt.x, y: pt.y }; - ScreenToClient(self.hwnd, &mut pt); } - (self.listener)(FileDropEvent::Dropped { - paths, - position: (pt.x as _, pt.y as _), - }); - Ok(()) } } diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 892b7704c..d7af073ba 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT -mod file_drop; +mod drag_drop; use std::{ borrow::Cow, cell::RefCell, collections::HashSet, fmt::Write, iter::once, @@ -37,7 +37,7 @@ use windows::{ }, }; -use self::file_drop::FileDropController; +use self::drag_drop::DragDropController; use super::Theme; use crate::{ proxy::ProxyConfig, Error, MemoryUsageLevel, PageLoadEvent, Rect, RequestAsyncResponder, Result, @@ -71,7 +71,7 @@ pub(crate) struct InnerWebView { // Store FileDropController in here to make sure it gets dropped when // the webview gets dropped, otherwise we'll have a memory leak #[allow(dead_code)] - file_drop_controller: Option, + drag_drop_controller: Option, } impl Drop for InnerWebView { @@ -126,7 +126,7 @@ impl InnerWebView { let (hwnd, bounds) = Self::create_container_hwnd(parent, &attributes, is_child)?; - let drop_handler = attributes.file_drop_handler.take(); + let drop_handler = attributes.drag_drop_handler.take(); let env = Self::create_environment(&web_context, pl_attrs.clone(), &attributes)?; let controller = Self::create_controller(hwnd, &env, attributes.incognito)?; @@ -141,7 +141,7 @@ impl InnerWebView { is_child, )?; - let file_drop_controller = drop_handler.map(|handler| FileDropController::new(hwnd, handler)); + let drag_drop_controller = drop_handler.map(|handler| DragDropController::new(hwnd, handler)); Ok(Self { parent: RefCell::new(parent), @@ -150,7 +150,7 @@ impl InnerWebView { is_child, webview, env, - file_drop_controller, + drag_drop_controller, }) } @@ -309,7 +309,7 @@ impl InnerWebView { CreateCoreWebView2EnvironmentWithOptions( PCWSTR::null(), - data_directory.unwrap_or_else(|| PCWSTR::null()), + data_directory.unwrap_or_else(PCWSTR::null), &options, &environmentcreatedhandler, ) @@ -409,7 +409,7 @@ impl InnerWebView { unsafe { Self::attach_custom_protocol_handler( &webview, - &env, + env, hwnd, scheme, &mut attributes, @@ -806,7 +806,7 @@ impl InnerWebView { if let Some((custom_protocol, custom_protocol_handler)) = custom_protocols .iter() - .find(|(protocol, _)| is_custom_protocol_uri(&uri, scheme, &protocol)) + .find(|(protocol, _)| is_custom_protocol_uri(&uri, scheme, protocol)) { let request = match Self::perpare_request(scheme, custom_protocol, &webview_request, &uri) { diff --git a/src/wkwebview/file_drop.rs b/src/wkwebview/drag_drop.rs similarity index 70% rename from src/wkwebview/file_drop.rs rename to src/wkwebview/drag_drop.rs index 33e6fc38a..948d83df7 100644 --- a/src/wkwebview/file_drop.rs +++ b/src/wkwebview/drag_drop.rs @@ -17,12 +17,15 @@ use objc::{ }; use once_cell::sync::Lazy; -use crate::FileDropEvent; +use crate::DragDropEvent; pub(crate) type NSDragOperation = cocoa::foundation::NSUInteger; + #[allow(non_upper_case_globals)] const NSDragOperationCopy: NSDragOperation = 1; +const DRAG_DROP_HANDLER_IVAR: &str = "DragDropHandler"; + static OBJC_DRAGGING_ENTERED: Lazy NSDragOperation> = Lazy::new(|| unsafe { std::mem::transmute(method_getImplementation(class_getInstanceMethod( @@ -55,19 +58,19 @@ static OBJC_DRAGGING_UPDATED: Lazy NSDr }); // Safety: objc runtime calls are unsafe -pub(crate) unsafe fn set_file_drop_handler( +pub(crate) unsafe fn set_drag_drop_handler( webview: *mut Object, - handler: Box bool>, -) -> *mut Box bool> { + handler: Box bool>, +) -> *mut Box bool> { let listener = Box::into_raw(Box::new(handler)); - (*webview).set_ivar("FileDropHandler", listener as *mut _ as *mut c_void); + (*webview).set_ivar(DRAG_DROP_HANDLER_IVAR, listener as *mut _ as *mut c_void); listener } #[allow(clippy::mut_from_ref)] -unsafe fn get_handler(this: &Object) -> &mut Box bool> { - let delegate: *mut c_void = *this.get_ivar("FileDropHandler"); - &mut *(delegate as *mut Box bool>) +unsafe fn get_handler(this: &Object) -> &mut Box bool> { + let delegate: *mut c_void = *this.get_ivar(DRAG_DROP_HANDLER_IVAR); + &mut *(delegate as *mut Box bool>) } unsafe fn collect_paths(drag_info: id) -> Vec { @@ -77,30 +80,38 @@ unsafe fn collect_paths(drag_info: id) -> Vec { }; let pb: id = msg_send![drag_info, draggingPasteboard]; - let mut file_drop_paths = Vec::new(); + let mut drag_drop_paths = Vec::new(); let types: id = msg_send![class!(NSArray), arrayWithObject: NSFilenamesPboardType]; if !NSPasteboard::availableTypeFromArray(pb, types).is_null() { for path in NSPasteboard::propertyListForType(pb, NSFilenamesPboardType).iter() { - file_drop_paths.push(PathBuf::from( + drag_drop_paths.push(PathBuf::from( CStr::from_ptr(NSString::UTF8String(path)) .to_string_lossy() .into_owned(), )); } } - file_drop_paths + drag_drop_paths } extern "C" fn dragging_updated(this: &mut Object, sel: Sel, drag_info: id) -> NSDragOperation { - let os_operation = OBJC_DRAGGING_UPDATED(this, sel, drag_info); - if os_operation == 0 { - // 0 will be returned for a file drop on any arbitrary location on the webview. - // We'll override that with NSDragOperationCopy. - NSDragOperationCopy + let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] }; + let frame: NSRect = unsafe { msg_send![this, frame] }; + let position = (dl.x as i32, (frame.size.height - dl.y) as i32); + let listener = unsafe { get_handler(this) }; + if !listener(DragDropEvent::Over { position }) { + let os_operation = OBJC_DRAGGING_UPDATED(this, sel, drag_info); + if os_operation == 0 { + // 0 will be returned for a drop on any arbitrary location on the webview. + // We'll override that with NSDragOperationCopy. + NSDragOperationCopy + } else { + // A different NSDragOperation is returned when a file is hovered over something like + // a , so we'll make sure to preserve that behaviour. + os_operation + } } else { - // A different NSDragOperation is returned when a file is hovered over something like - // a , so we'll make sure to preserve that behaviour. - os_operation + NSDragOperationCopy } } @@ -112,7 +123,7 @@ extern "C" fn dragging_entered(this: &mut Object, sel: Sel, drag_info: id) -> NS let frame: NSRect = unsafe { msg_send![this, frame] }; let position = (dl.x as i32, (frame.size.height - dl.y) as i32); - if !listener(FileDropEvent::Hovered { paths, position }) { + if !listener(DragDropEvent::Enter { paths, position }) { // Reject the Wry file drop (invoke the OS default behaviour) OBJC_DRAGGING_ENTERED(this, sel, drag_info) } else { @@ -128,8 +139,8 @@ extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id) let frame: NSRect = unsafe { msg_send![this, frame] }; let position = (dl.x as i32, (frame.size.height - dl.y) as i32); - if !listener(FileDropEvent::Dropped { paths, position }) { - // Reject the Wry file drop (invoke the OS default behaviour) + if !listener(DragDropEvent::Drop { paths, position }) { + // Reject the Wry drop (invoke the OS default behaviour) OBJC_PERFORM_DRAG_OPERATION(this, sel, drag_info) } else { YES @@ -138,23 +149,23 @@ extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id) extern "C" fn dragging_exited(this: &mut Object, sel: Sel, drag_info: id) { let listener = unsafe { get_handler(this) }; - if !listener(FileDropEvent::Cancelled) { - // Reject the Wry file drop (invoke the OS default behaviour) + if !listener(DragDropEvent::Leave) { + // Reject the Wry drop (invoke the OS default behaviour) OBJC_DRAGGING_EXITED(this, sel, drag_info); } } -pub(crate) unsafe fn add_file_drop_methods(decl: &mut ClassDecl) { - decl.add_ivar::<*mut c_void>("FileDropHandler"); +pub(crate) unsafe fn add_drag_drop_methods(decl: &mut ClassDecl) { + decl.add_ivar::<*mut c_void>(DRAG_DROP_HANDLER_IVAR); decl.add_method( - sel!(draggingUpdated:), - dragging_updated as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation, + sel!(draggingEntered:), + dragging_entered as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation, ); decl.add_method( - sel!(draggingEntered:), - dragging_entered as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation, + sel!(draggingUpdated:), + dragging_updated as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation, ); decl.add_method( diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 06bda840f..97dbd20af 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -4,7 +4,7 @@ mod download; #[cfg(target_os = "macos")] -mod file_drop; +mod drag_drop; mod navigation; #[cfg(feature = "mac-proxy")] mod proxy; @@ -36,7 +36,7 @@ use objc::{ use objc_id::Id; #[cfg(target_os = "macos")] -use file_drop::{add_file_drop_methods, set_file_drop_handler}; +use drag_drop::{add_drag_drop_methods, set_drag_drop_handler}; #[cfg(feature = "mac-proxy")] use crate::{ @@ -82,7 +82,7 @@ pub(crate) struct InnerWebView { navigation_decide_policy_ptr: *mut Box bool>, page_load_handler: *mut Box, #[cfg(target_os = "macos")] - file_drop_ptr: *mut Box bool>, + drag_drop_ptr: *mut Box bool>, download_delegate: id, protocol_ptrs: Vec<*mut Box>, RequestAsyncResponder)>>, } @@ -342,7 +342,7 @@ impl InnerWebView { Some(mut decl) => { #[cfg(target_os = "macos")] { - add_file_drop_methods(&mut decl); + add_drag_drop_methods(&mut decl); synthetic_mouse_events::setup(&mut decl); decl.add_ivar::(ACCEPT_FIRST_MOUSE); decl.add_method( @@ -818,11 +818,11 @@ impl InnerWebView { // File drop handling #[cfg(target_os = "macos")] - let file_drop_ptr = match attributes.file_drop_handler { - // if we have a file_drop_handler defined, use the defined handler - Some(file_drop_handler) => set_file_drop_handler(webview, file_drop_handler), + let drag_drop_ptr = match attributes.drag_drop_handler { + // if we have a drag_drop_handler defined, use the defined handler + Some(drag_drop_handler) => set_drag_drop_handler(webview, drag_drop_handler), // prevent panic by using a blank handler - None => set_file_drop_handler(webview, Box::new(|_| false)), + None => set_drag_drop_handler(webview, Box::new(|_| false)), }; // ns window is required for the print operation @@ -848,7 +848,7 @@ impl InnerWebView { document_title_changed_handler, navigation_decide_policy_ptr, #[cfg(target_os = "macos")] - file_drop_ptr, + drag_drop_ptr, page_load_handler, download_delegate, protocol_ptrs, @@ -1213,17 +1213,6 @@ pub fn platform_webview_version() -> Result { } } -pub fn platform_webview_system_version() -> Result { - platform_webview_version().map(|webview_version| { - let webview_system_and_major_version = webview_version.split('.').next().unwrap(); - if webview_system_and_major_version.chars().count() < 5 { - webview_system_and_major_version[..1].to_string() - } else { - webview_system_and_major_version[..2].to_string() - } - }) -} - impl Drop for InnerWebView { fn drop(&mut self) { // We need to drop handler closures here @@ -1246,8 +1235,8 @@ impl Drop for InnerWebView { drop_navigation_methods(self); #[cfg(target_os = "macos")] - if !self.file_drop_ptr.is_null() { - drop(Box::from_raw(self.file_drop_ptr)); + if !self.drag_drop_ptr.is_null() { + drop(Box::from_raw(self.drag_drop_ptr)); } if !self.download_delegate.is_null() {