Skip to content

Commit

Permalink
migrate drag & drop
Browse files Browse the repository at this point in the history
  • Loading branch information
pewsheen committed Mar 27, 2024
1 parent fdbd3d3 commit 0da1952
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 70 deletions.
12 changes: 12 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ cocoa = "0.25"
core-graphics = "0.23"
objc = "0.2"
objc_id = "0.1"
block2 = "0.4"
objc2 = "0.5"
icrate = { version = "0.1.0", features = [
"Foundation",
"Foundation_NSURLRequest",
"AppKit_NSView",
"AppKit_NSPasteboard",
"WebKit",
"WebKit_WKWebView",
"WebKit_WKWebViewConfiguration",
"WebKit_WKWebsiteDataStore"
] }

[target."cfg(target_os = \"android\")".dependencies]
crossbeam-channel = "0.5"
Expand Down
171 changes: 102 additions & 69 deletions src/wkwebview/drag_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,85 +7,102 @@ use std::{
path::PathBuf,
};

use cocoa::{
base::{id, BOOL, YES},
foundation::{NSPoint, NSRect},
use icrate::{
AppKit::{NSDragOperation, NSDraggingInfo, NSFilenamesPboardType, NSView},
Foundation::{NSArray, NSPoint, NSRect, NSString},
};
use objc::{
declare::ClassDecl,
runtime::{class_getInstanceMethod, method_getImplementation, Object, Sel},
use objc2::{
class,
declare::ClassBuilder,
rc::Id,
runtime::{AnyObject, Bool, ProtocolObject, Sel},
};
use once_cell::sync::Lazy;

use crate::DragDropEvent;

pub(crate) type NSDragOperation = cocoa::foundation::NSUInteger;
// 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<extern "C" fn(*const Object, Sel, id) -> NSDragOperation> =
Lazy::new(|| unsafe {
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
class!(WKWebView),
sel!(draggingEntered:),
)))
});

static OBJC_DRAGGING_EXITED: Lazy<extern "C" fn(*const Object, Sel, id)> = Lazy::new(|| unsafe {
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
class!(WKWebView),
sel!(draggingExited:),
)))
static OBJC_DRAGGING_ENTERED: Lazy<
extern "C" fn(&NSView, Sel, &ProtocolObject<dyn NSDraggingInfo>) -> NSDragOperation,
> = Lazy::new(|| unsafe {
std::mem::transmute(
class!(WKWebView)
.instance_method(objc2::sel!(draggingEntered:))
.unwrap()
.implementation(),
)
});

static OBJC_PERFORM_DRAG_OPERATION: Lazy<extern "C" fn(*const Object, Sel, id) -> BOOL> =
Lazy::new(|| unsafe {
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
class!(WKWebView),
sel!(performDragOperation:),
)))
});

static OBJC_DRAGGING_UPDATED: Lazy<extern "C" fn(*const Object, Sel, id) -> NSDragOperation> =
Lazy::new(|| unsafe {
std::mem::transmute(method_getImplementation(class_getInstanceMethod(
class!(WKWebView),
sel!(draggingUpdated:),
)))
});
static OBJC_DRAGGING_EXITED: Lazy<
extern "C" fn(&NSView, Sel, &ProtocolObject<dyn NSDraggingInfo>),
> = Lazy::new(|| unsafe {
std::mem::transmute(
class!(WKWebView)
.instance_method(objc2::sel!(draggingExited:))
.unwrap()
.implementation(),
)
});

static OBJC_PERFORM_DRAG_OPERATION: Lazy<
extern "C" fn(&NSView, Sel, &ProtocolObject<dyn NSDraggingInfo>) -> objc2::runtime::Bool,
> = Lazy::new(|| unsafe {
std::mem::transmute(
class!(WKWebView)
.instance_method(objc2::sel!(performDragOperation:))
.unwrap()
.implementation(),
)
});

static OBJC_DRAGGING_UPDATED: Lazy<
extern "C" fn(&NSView, Sel, &ProtocolObject<dyn NSDraggingInfo>) -> NSDragOperation,
> = Lazy::new(|| unsafe {
std::mem::transmute(
class!(WKWebView)
.instance_method(objc2::sel!(draggingUpdated:))
.unwrap()
.implementation(),
)
});

// Safety: objc runtime calls are unsafe
pub(crate) unsafe fn set_drag_drop_handler(
webview: *mut Object,
webview: *mut AnyObject,
handler: Box<dyn Fn(DragDropEvent) -> bool>,
) -> *mut Box<dyn Fn(DragDropEvent) -> bool> {
let listener = Box::into_raw(Box::new(handler));
(*webview).set_ivar(DRAG_DROP_HANDLER_IVAR, listener as *mut _ as *mut c_void);
let ivar = (*webview)
.class()
.instance_variable(DRAG_DROP_HANDLER_IVAR)
.unwrap();
let mut r = ivar.load_mut::<*mut c_void>(webview.as_mut().unwrap());
r.replace(*(listener 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<dyn Fn(DragDropEvent) -> bool> {
unsafe fn get_handler(this: &AnyObject) -> &mut Box<dyn Fn(DragDropEvent) -> bool> {
let delegate: *mut c_void = *this.get_ivar(DRAG_DROP_HANDLER_IVAR);
&mut *(delegate as *mut Box<dyn Fn(DragDropEvent) -> bool>)
}

unsafe fn collect_paths(drag_info: id) -> Vec<PathBuf> {
use cocoa::{
appkit::{NSFilenamesPboardType, NSPasteboard},
foundation::{NSFastEnumeration, NSString},
};

let pb: id = msg_send![drag_info, draggingPasteboard];
unsafe fn collect_paths(drag_info: &ProtocolObject<dyn NSDraggingInfo>) -> Vec<PathBuf> {
let pb = drag_info.draggingPasteboard();
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() {
let types = NSArray::arrayWithObject(NSFilenamesPboardType);
if pb.availableTypeFromArray(&types).is_some() {
for path in pb.propertyListForType(NSFilenamesPboardType).iter() {
let path = Id::<AnyObject>::cast(*path) as Id<NSString>;
drag_drop_paths.push(PathBuf::from(
CStr::from_ptr(NSString::UTF8String(path))
CStr::from_ptr(NSString::UTF8String(&path))
.to_string_lossy()
.into_owned(),
));
Expand All @@ -94,9 +111,13 @@ unsafe fn collect_paths(drag_info: id) -> Vec<PathBuf> {
drag_drop_paths
}

extern "C" fn dragging_updated(this: &mut Object, sel: Sel, drag_info: id) -> NSDragOperation {
let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
let frame: NSRect = unsafe { msg_send![this, frame] };
extern "C" fn dragging_updated(
this: &NSView,
sel: Sel,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> NSDragOperation {
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = unsafe { 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 }) {
Expand All @@ -115,12 +136,16 @@ extern "C" fn dragging_updated(this: &mut Object, sel: Sel, drag_info: id) -> NS
}
}

extern "C" fn dragging_entered(this: &mut Object, sel: Sel, drag_info: id) -> NSDragOperation {
extern "C" fn dragging_entered(
this: &NSView,
sel: Sel,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> NSDragOperation {
let listener = unsafe { get_handler(this) };
let paths = unsafe { collect_paths(drag_info) };

let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
let frame: NSRect = unsafe { msg_send![this, frame] };
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = unsafe { this.frame() };
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);

if !listener(DragDropEvent::Enter { paths, position }) {
Expand All @@ -131,50 +156,58 @@ extern "C" fn dragging_entered(this: &mut Object, sel: Sel, drag_info: id) -> NS
}
}

extern "C" fn perform_drag_operation(this: &mut Object, sel: Sel, drag_info: id) -> BOOL {
extern "C" fn perform_drag_operation(
this: &NSView,
sel: Sel,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) -> Bool {
let listener = unsafe { get_handler(this) };
let paths = unsafe { collect_paths(drag_info) };

let dl: NSPoint = unsafe { msg_send![drag_info, draggingLocation] };
let frame: NSRect = unsafe { msg_send![this, frame] };
let dl: NSPoint = unsafe { drag_info.draggingLocation() };
let frame: NSRect = unsafe { this.frame() };
let position = (dl.x as i32, (frame.size.height - dl.y) as i32);

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
Bool::YES
}
}

extern "C" fn dragging_exited(this: &mut Object, sel: Sel, drag_info: id) {
extern "C" fn dragging_exited(
this: &NSView,
sel: Sel,
drag_info: &ProtocolObject<dyn NSDraggingInfo>,
) {
let listener = unsafe { get_handler(this) };
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_drag_drop_methods(decl: &mut ClassDecl) {
pub(crate) unsafe fn add_drag_drop_methods(decl: &mut ClassBuilder) {
decl.add_ivar::<*mut c_void>(DRAG_DROP_HANDLER_IVAR);

decl.add_method(
sel!(draggingEntered:),
dragging_entered as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation,
objc2::sel!(draggingEntered:),
dragging_entered as extern "C" fn(_, _, _) -> NSDragOperation,
);

decl.add_method(
sel!(draggingUpdated:),
dragging_updated as extern "C" fn(&mut Object, Sel, id) -> NSDragOperation,
objc2::sel!(draggingUpdated:),
dragging_updated as extern "C" fn(_, _, _) -> NSDragOperation,
);

decl.add_method(
sel!(performDragOperation:),
perform_drag_operation as extern "C" fn(&mut Object, Sel, id) -> BOOL,
objc2::sel!(performDragOperation:),
perform_drag_operation as extern "C" fn(_, _, _) -> Bool,
);

decl.add_method(
sel!(draggingExited:),
dragging_exited as extern "C" fn(&mut Object, Sel, id),
objc2::sel!(draggingExited:),
dragging_exited as extern "C" fn(_, _, _),
);
}
3 changes: 2 additions & 1 deletion src/wkwebview/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use cocoa::{
base::{id, nil, NO, YES},
foundation::{NSDictionary, NSFastEnumeration, NSInteger},
};
// use objc2::class;
use raw_window_handle::{HasWindowHandle, RawWindowHandle};

use std::{
Expand Down Expand Up @@ -339,7 +340,7 @@ impl InnerWebView {

// WebView and manager
let manager: id = msg_send![config, userContentController];
let cls = match ClassDecl::new("WryWebView", class!(WKWebView)) {
let cls = match objc::declare::ClassDecl::new("WryWebView", class!(WKWebView)) {
#[allow(unused_mut)]
Some(mut decl) => {
#[cfg(target_os = "macos")]
Expand Down

0 comments on commit 0da1952

Please sign in to comment.