From ac062d0c977de7ca648504ef6238a8a016076aa6 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 17 Aug 2024 16:23:13 -0400 Subject: [PATCH] UI/AppKit: Process drag-and-drop events through the web view This forwards all drag-and-drop events from the UI to the WebContent process. If the page accepts the events, the UI does not handle them. Otherwise, we will open the dropped files as file:// URLs. --- Ladybird/AppKit/UI/Event.h | 8 ++- Ladybird/AppKit/UI/Event.mm | 65 +++++++++++++++++++- Ladybird/AppKit/UI/LadybirdWebView.mm | 60 +++++++++++++++++- Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp | 7 +++ Ladybird/AppKit/UI/LadybirdWebViewBridge.h | 1 + 5 files changed, 138 insertions(+), 3 deletions(-) diff --git a/Ladybird/AppKit/UI/Event.h b/Ladybird/AppKit/UI/Event.h index 64689b13157..9f3e66fd61f 100644 --- a/Ladybird/AppKit/UI/Event.h +++ b/Ladybird/AppKit/UI/Event.h @@ -1,11 +1,13 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include +#include #include #import @@ -13,6 +15,10 @@ namespace Ladybird { Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView*, NSScrollView*, Web::UIEvents::MouseButton); + +Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type, id, NSView*); +Vector drag_event_url_list(Web::DragEvent const&); + Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*); NSEvent* key_event_to_ns_event(Web::KeyEvent const&); diff --git a/Ladybird/AppKit/UI/Event.mm b/Ladybird/AppKit/UI/Event.mm index 5422d33a9be..4812499189a 100644 --- a/Ladybird/AppKit/UI/Event.mm +++ b/Ladybird/AppKit/UI/Event.mm @@ -1,11 +1,14 @@ /* - * Copyright (c) 2023, Tim Flynn + * Copyright (c) 2023-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include +#include +#include +#include #import #import @@ -70,6 +73,66 @@ Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type type, NSEvent* eve return { type, device_position, device_screen_position, button, button, modifiers, wheel_delta_x, wheel_delta_y, nullptr }; } +struct DragData : public Web::ChromeInputData { + explicit DragData(Vector urls) + : urls(move(urls)) + { + } + + Vector urls; +}; + +Web::DragEvent ns_event_to_drag_event(Web::DragEvent::Type type, id event, NSView* view) +{ + auto position = [view convertPoint:event.draggingLocation fromView:nil]; + auto device_position = ns_point_to_gfx_point(position).to_type(); + + auto screen_position = [NSEvent mouseLocation]; + auto device_screen_position = ns_point_to_gfx_point(screen_position).to_type(); + + auto button = Web::UIEvents::MouseButton::Primary; + auto modifiers = ns_modifiers_to_key_modifiers([NSEvent modifierFlags], button); + + Vector files; + OwnPtr chrome_data; + + auto for_each_file = [&](auto callback) { + NSArray* file_list = [[event draggingPasteboard] readObjectsForClasses:@[ [NSURL class] ] + options:nil]; + + for (NSURL* file in file_list) { + auto file_path = Ladybird::ns_string_to_byte_string([file path]); + callback(file_path); + } + }; + + if (type == Web::DragEvent::Type::DragStart) { + for_each_file([&](ByteString const& file_path) { + if (auto file = Web::HTML::SelectedFile::from_file_path(file_path); file.is_error()) + warnln("Unable to open file {}: {}", file_path, file.error()); + else + files.append(file.release_value()); + }); + } else if (type == Web::DragEvent::Type::Drop) { + Vector urls; + + for_each_file([&](ByteString const& file_path) { + if (auto url = URL::create_with_url_or_path(file_path); url.is_valid()) + urls.append(move(url)); + }); + + chrome_data = make(move(urls)); + } + + return { type, device_position, device_screen_position, button, button, modifiers, move(files), move(chrome_data) }; +} + +Vector drag_event_url_list(Web::DragEvent const& event) +{ + auto& chrome_data = verify_cast(*event.chrome_data); + return move(chrome_data.urls); +} + NSEvent* create_context_menu_mouse_event(NSView* view, Gfx::IntPoint position) { return create_context_menu_mouse_event(view, gfx_point_to_ns_point(position)); diff --git a/Ladybird/AppKit/UI/LadybirdWebView.mm b/Ladybird/AppKit/UI/LadybirdWebView.mm index da617cc03fe..ca9d07c7d97 100644 --- a/Ladybird/AppKit/UI/LadybirdWebView.mm +++ b/Ladybird/AppKit/UI/LadybirdWebView.mm @@ -48,7 +48,7 @@ struct HideCursor { } }; -@interface LadybirdWebView () +@interface LadybirdWebView () { OwnPtr m_web_view_bridge; @@ -114,6 +114,8 @@ struct HideCursor { owner:self userInfo:nil]; [self addTrackingArea:area]; + + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeFileURL, nil]]; } return self; @@ -417,6 +419,25 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ self.event_being_redispatched = nil; }; + m_web_view_bridge->on_finish_handling_drag_event = [weak_self](auto const& event) { + LadybirdWebView* self = weak_self; + if (self == nil) { + return; + } + + if (event.type != Web::DragEvent::Type::Drop) { + return; + } + + if (auto urls = Ladybird::drag_event_url_list(event); !urls.is_empty()) { + [self.observer loadURL:urls[0]]; + + for (size_t i = 1; i < urls.size(); ++i) { + [self.observer onCreateNewTab:urls[i] activateTab:Web::HTML::ActivateTab::No]; + } + } + }; + m_web_view_bridge->on_cursor_change = [weak_self](auto cursor) { LadybirdWebView* self = weak_self; if (self == nil) { @@ -1669,4 +1690,41 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ m_web_view_bridge->enqueue_input_event(move(key_event)); } +#pragma mark - NSDraggingDestination + +- (NSDragOperation)draggingEntered:(id)event +{ + auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragStart, event, self); + m_web_view_bridge->enqueue_input_event(move(drag_event)); + + return NSDragOperationCopy; +} + +- (NSDragOperation)draggingUpdated:(id)event +{ + auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragMove, event, self); + m_web_view_bridge->enqueue_input_event(move(drag_event)); + + return NSDragOperationCopy; +} + +- (void)draggingExited:(id)event +{ + auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::DragEnd, event, self); + m_web_view_bridge->enqueue_input_event(move(drag_event)); +} + +- (BOOL)performDragOperation:(id)event +{ + auto drag_event = Ladybird::ns_event_to_drag_event(Web::DragEvent::Type::Drop, event, self); + m_web_view_bridge->enqueue_input_event(move(drag_event)); + + return YES; +} + +- (BOOL)wantsPeriodicDraggingUpdates +{ + return NO; +} + @end diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp index bd7f75b9b0c..5bed92dafe8 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp @@ -93,6 +93,13 @@ void WebViewBridge::enqueue_input_event(Web::MouseEvent event) ViewImplementation::enqueue_input_event(move(event)); } +void WebViewBridge::enqueue_input_event(Web::DragEvent event) +{ + event.position = to_content_position(event.position.to_type()).to_type(); + event.screen_position = to_content_position(event.screen_position.to_type()).to_type(); + ViewImplementation::enqueue_input_event(move(event)); +} + void WebViewBridge::enqueue_input_event(Web::KeyEvent event) { ViewImplementation::enqueue_input_event(move(event)); diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h index 6a383ff37d0..d63d73b9709 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h @@ -44,6 +44,7 @@ public: void set_preferred_motion(Web::CSS::PreferredMotion); void enqueue_input_event(Web::MouseEvent); + void enqueue_input_event(Web::DragEvent); void enqueue_input_event(Web::KeyEvent); struct Paintable {