1- //! Native drag interception for macOS via method swizzling on WryWebView .
1+ //! Native drag interception for macOS via method swizzling on wry's webview class .
22//!
33//! Swizzles `draggingEntered:`, `draggingUpdated:`, and `draggingExited:` to:
44//! 1. Read drag image dimensions via `enumerateDraggingItems` (for overlay suppression)
1111//!
1212//! ## Resilience
1313//!
14- //! All native API calls are guarded against class/method removal. If wry renames its
15- //! internal webview class or macOS deprecates APIs we rely on, the swizzle degrades gracefully:
14+ //! All native API calls are guarded against method removal. The webview class is discovered
15+ //! from the actual webview instance (not a hardcoded name), so wry version changes are safe.
16+ //! If macOS deprecates APIs we rely on, the swizzle degrades gracefully:
1617//! - Drag image detection disabled → the DOM overlay is always shown (redundant but functional)
1718//! - Modifier key detection disabled → falls back to JS keydown/keyup (works when webview has focus)
1819//! - Image swapping disabled → self-drags show the OS drag image over the window (functional)
@@ -30,7 +31,7 @@ use objc2::{msg_send, sel};
3031use objc2_app_kit:: { NSDragOperation , NSDraggingItem , NSDraggingItemEnumerationOptions } ;
3132use objc2_foundation:: { NSDictionary , NSInteger , NSRect , NSSize } ;
3233use serde:: Serialize ;
33- use tauri:: { AppHandle , Emitter } ;
34+ use tauri:: { AppHandle , Emitter , Manager } ;
3435
3536use crate :: drag_image_swap;
3637
@@ -72,69 +73,107 @@ pub(crate) fn warn_once(flag: &AtomicBool, msg: &str) {
7273 }
7374}
7475
75- /// Installs swizzles on WryWebView. Call once during app setup.
76+ /// Installs swizzles on wry's webview class. Call once during app setup after the
77+ /// webview is created (e.g. `RunEvent::Ready`).
78+ ///
79+ /// Gets the ObjC class from the actual webview instance rather than looking up a
80+ /// hardcoded class name, so this is resilient to wry renaming its internal class.
7681pub fn install ( app_handle : AppHandle ) {
77- APP_HANDLE . set ( app_handle) . ok ( ) ;
82+ APP_HANDLE . set ( app_handle. clone ( ) ) . ok ( ) ;
7883
79- unsafe {
80- let Some ( cls) = AnyClass :: get ( c"WryWebView" ) else {
81- log:: warn!(
82- "drag_image_detection: WryWebView class not found — swizzle skipped. \
83- Drag image detection and modifier tracking during drags are disabled. \
84- This is likely caused by a wry update that renamed the webview class; \
85- search wry's source for the ObjC class name and update the c\" WryWebView\" \
86- lookup in drag_image_detection.rs."
87- ) ;
88- return ;
89- } ;
84+ let Some ( ( _label, webview_window) ) = app_handle. webview_windows ( ) . into_iter ( ) . next ( ) else {
85+ log:: warn!(
86+ "drag_image_detection: no webview windows found — swizzle skipped. \
87+ Drag image detection and modifier tracking during drags are disabled."
88+ ) ;
89+ return ;
90+ } ;
91+
92+ if let Err ( e) = webview_window. with_webview ( |webview| {
93+ unsafe { install_swizzles ( webview. inner ( ) ) } ;
94+ } ) {
95+ log:: warn!(
96+ "drag_image_detection: with_webview failed ({e}) — swizzle skipped. \
97+ Drag image detection and modifier tracking during drags are disabled."
98+ ) ;
99+ }
100+ }
90101
91- // Swizzle draggingEntered:
92- if let Some ( method) = cls. instance_method ( sel ! ( draggingEntered: ) ) {
93- ORIGINAL_ENTERED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
102+ /// Performs the actual swizzle installation given the native webview pointer.
103+ ///
104+ /// # Safety
105+ /// `webview_ptr` must be a valid pointer to the ObjC webview object (from `PlatformWebview::inner()`).
106+ unsafe fn install_swizzles ( webview_ptr : * mut std:: ffi:: c_void ) {
107+ let obj = webview_ptr as * const AnyObject ;
108+ if obj. is_null ( ) {
109+ log:: warn!(
110+ "drag_image_detection: native webview pointer is null — swizzle skipped. \
111+ Drag image detection and modifier tracking during drags are disabled."
112+ ) ;
113+ return ;
114+ }
115+
116+ let cls: * const AnyClass = unsafe { msg_send ! [ obj, class] } ;
117+ let Some ( cls) = ( unsafe { cls. as_ref ( ) } ) else {
118+ log:: warn!(
119+ "drag_image_detection: could not get ObjC class from webview — swizzle skipped. \
120+ Drag image detection and modifier tracking during drags are disabled."
121+ ) ;
122+ return ;
123+ } ;
124+
125+ // Swizzle draggingEntered:
126+ if let Some ( method) = cls. instance_method ( sel ! ( draggingEntered: ) ) {
127+ ORIGINAL_ENTERED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
128+ unsafe {
94129 method. set_implementation ( std:: mem:: transmute :: < * const ( ) , Imp > (
95130 swizzled_dragging_entered as * const ( ) ,
96131 ) ) ;
97- } else {
98- log:: warn!(
99- "drag_image_detection: draggingEntered: not found on WryWebView — \
100- drag image size detection is disabled. \
101- Wry may have changed how it implements NSDraggingDestination; \
102- check wry's drag-and-drop event handling in its ObjC layer."
103- ) ;
104132 }
133+ } else {
134+ log:: warn!(
135+ "drag_image_detection: draggingEntered: not found on webview class — \
136+ drag image size detection is disabled. \
137+ Wry may have changed how it implements NSDraggingDestination; \
138+ check wry's drag-and-drop event handling in its ObjC layer."
139+ ) ;
140+ }
105141
106- // Swizzle draggingUpdated:
107- if let Some ( method) = cls. instance_method ( sel ! ( draggingUpdated: ) ) {
108- ORIGINAL_UPDATED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
142+ // Swizzle draggingUpdated:
143+ if let Some ( method) = cls. instance_method ( sel ! ( draggingUpdated: ) ) {
144+ ORIGINAL_UPDATED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
145+ unsafe {
109146 method. set_implementation ( std:: mem:: transmute :: < * const ( ) , Imp > (
110147 swizzled_dragging_updated as * const ( ) ,
111148 ) ) ;
112- } else {
113- log:: warn!(
114- "drag_image_detection: draggingUpdated: not found on WryWebView — \
115- live modifier key tracking during drags is disabled. \
116- Wry may have changed how it implements NSDraggingDestination; \
117- check wry's drag-and-drop event handling in its ObjC layer."
118- ) ;
119149 }
150+ } else {
151+ log:: warn!(
152+ "drag_image_detection: draggingUpdated: not found on webview class — \
153+ live modifier key tracking during drags is disabled. \
154+ Wry may have changed how it implements NSDraggingDestination; \
155+ check wry's drag-and-drop event handling in its ObjC layer."
156+ ) ;
157+ }
120158
121- // Swizzle draggingExited: for self-drag image swapping (transparent → rich on window exit)
122- if let Some ( method) = cls. instance_method ( sel ! ( draggingExited: ) ) {
123- ORIGINAL_EXITED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
159+ // Swizzle draggingExited: for self-drag image swapping (transparent → rich on window exit)
160+ if let Some ( method) = cls. instance_method ( sel ! ( draggingExited: ) ) {
161+ ORIGINAL_EXITED_IMP . set ( method. implementation ( ) ) . ok ( ) ;
162+ unsafe {
124163 method. set_implementation ( std:: mem:: transmute :: < * const ( ) , Imp > (
125164 swizzled_dragging_exited as * const ( ) ,
126165 ) ) ;
127- } else {
128- log:: warn!(
129- "drag_image_detection: draggingExited: not found on WryWebView — \
130- drag image swapping on window exit is disabled. \
131- Wry may have changed how it implements NSDraggingDestination; \
132- check wry's drag-and-drop event handling in its ObjC layer."
133- ) ;
134166 }
135-
136- log:: debug!( "drag_image_detection: swizzles installed on WryWebView" ) ;
167+ } else {
168+ log:: warn!(
169+ "drag_image_detection: draggingExited: not found on webview class — \
170+ drag image swapping on window exit is disabled. \
171+ Wry may have changed how it implements NSDraggingDestination; \
172+ check wry's drag-and-drop event handling in its ObjC layer."
173+ ) ;
137174 }
175+
176+ log:: debug!( "drag_image_detection: swizzles installed on {:?}" , cls. name( ) ) ;
138177}
139178
140179// --- Modifier key detection ---
0 commit comments