From 2c31ef11bcdab539b19cfb1f0f022511bfc5b80d Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 5 Mar 2024 16:11:22 -0500 Subject: [PATCH] Ladybird/AppKit: Handle input events through LibWebView The AppKit chrome currently handles all input events before selectively forwarding those events to WebContent. This means that WebContent does not see events like cmd+c. Here, we make use of LibWebView's input handling and wait for LibWebView to inform the chrome that it should handle the event itself. --- Ladybird/AppKit/UI/Event.h | 21 +---- Ladybird/AppKit/UI/Event.mm | 64 +++++++++++++- Ladybird/AppKit/UI/LadybirdWebView.mm | 93 ++++++++++++-------- Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp | 35 ++------ Ladybird/AppKit/UI/LadybirdWebViewBridge.h | 15 +--- 5 files changed, 130 insertions(+), 98 deletions(-) diff --git a/Ladybird/AppKit/UI/Event.h b/Ladybird/AppKit/UI/Event.h index d710b0236f..1ec3423a74 100644 --- a/Ladybird/AppKit/UI/Event.h +++ b/Ladybird/AppKit/UI/Event.h @@ -6,30 +6,17 @@ #pragma once -// FIXME: These should not be included outside of Serenity. -#include -#include +#include #import namespace Ladybird { -struct MouseEvent { - Gfx::IntPoint position {}; - Gfx::IntPoint screen_position {}; - GUI::MouseButton button { GUI::MouseButton::Primary }; - KeyModifier modifiers { KeyModifier::Mod_None }; -}; -MouseEvent ns_event_to_mouse_event(NSEvent*, NSView*, GUI::MouseButton); +Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type, NSEvent*, NSView*, NSScrollView*, GUI::MouseButton); +Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type, NSEvent*); +NSEvent* key_event_to_ns_event(Web::KeyEvent const&); NSEvent* create_context_menu_mouse_event(NSView*, Gfx::IntPoint); NSEvent* create_context_menu_mouse_event(NSView*, NSPoint); -struct KeyEvent { - KeyCode key_code { KeyCode::Key_Invalid }; - KeyModifier modifiers { KeyModifier::Mod_None }; - u32 code_point { 0 }; -}; -KeyEvent ns_event_to_key_event(NSEvent*); - } diff --git a/Ladybird/AppKit/UI/Event.mm b/Ladybird/AppKit/UI/Event.mm index 82dd588809..56f910573c 100644 --- a/Ladybird/AppKit/UI/Event.mm +++ b/Ladybird/AppKit/UI/Event.mm @@ -36,13 +36,37 @@ static KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags modifier_f return static_cast(modifiers); } -MouseEvent ns_event_to_mouse_event(NSEvent* event, NSView* view, GUI::MouseButton button) +Web::MouseEvent ns_event_to_mouse_event(Web::MouseEvent::Type type, NSEvent* event, NSView* view, NSScrollView* scroll_view, GUI::MouseButton button) { auto position = [view convertPoint:event.locationInWindow 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 modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags, button); - return { ns_point_to_gfx_point(position), ns_point_to_gfx_point(screen_position), button, modifiers }; + int wheel_delta_x = 0; + int wheel_delta_y = 0; + + if (type == Web::MouseEvent::Type::MouseDown) { + if (event.clickCount % 2 == 0) { + type = Web::MouseEvent::Type::DoubleClick; + } + } else if (type == Web::MouseEvent::Type::MouseWheel) { + CGFloat delta_x = -[event scrollingDeltaX]; + CGFloat delta_y = -[event scrollingDeltaY]; + + if (![event hasPreciseScrollingDeltas]) { + delta_x *= scroll_view.horizontalLineScroll; + delta_y *= scroll_view.verticalLineScroll; + } + + wheel_delta_x = static_cast(delta_x); + wheel_delta_y = static_cast(delta_y); + } + + return { type, device_position, device_screen_position, button, button, modifiers, wheel_delta_x, wheel_delta_y, nullptr }; } NSEvent* create_context_menu_mouse_event(NSView* view, Gfx::IntPoint position) @@ -179,7 +203,33 @@ static KeyCode ns_key_code_to_key_code(unsigned short key_code, KeyModifier& mod return KeyCode::Key_Invalid; } -KeyEvent ns_event_to_key_event(NSEvent* event) +class KeyData : public Web::ChromeInputData { +public: + explicit KeyData(NSEvent* event) + : m_event(CFBridgingRetain(event)) + { + } + + virtual ~KeyData() override + { + if (m_event != nullptr) { + CFBridgingRelease(m_event); + } + } + + NSEvent* take_event() + { + VERIFY(m_event != nullptr); + + CFTypeRef event = exchange(m_event, nullptr); + return CFBridgingRelease(event); + } + +private: + CFTypeRef m_event { nullptr }; +}; + +Web::KeyEvent ns_event_to_key_event(Web::KeyEvent::Type type, NSEvent* event) { auto modifiers = ns_modifiers_to_key_modifiers(event.modifierFlags); auto key_code = ns_key_code_to_key_code(event.keyCode, modifiers); @@ -190,7 +240,13 @@ KeyEvent ns_event_to_key_event(NSEvent* event) // FIXME: WebContent should really support multi-code point key events. auto code_point = utf8_view.is_empty() ? 0u : *utf8_view.begin(); - return { key_code, modifiers, code_point }; + return { type, key_code, modifiers, code_point, make(event) }; +} + +NSEvent* key_event_to_ns_event(Web::KeyEvent const& event) +{ + auto& chrome_data = verify_cast(*event.chrome_data); + return chrome_data.take_event(); } } diff --git a/Ladybird/AppKit/UI/LadybirdWebView.mm b/Ladybird/AppKit/UI/LadybirdWebView.mm index 078700c3d4..71bf91ec61 100644 --- a/Ladybird/AppKit/UI/LadybirdWebView.mm +++ b/Ladybird/AppKit/UI/LadybirdWebView.mm @@ -67,6 +67,11 @@ struct HideCursor { @property (nonatomic, strong) NSTextField* status_label; @property (nonatomic, strong) NSAlert* dialog; +// NSEvent does not provide a way to mark whether it has been handled, nor can we attach user data to the event. So +// when we dispatch the event for a second time after WebContent has had a chance to handle it, we must track that +// event ourselves to prevent indefinitely repeating the event. +@property (nonatomic, strong) NSEvent* event_being_redispatched; + @end @implementation LadybirdWebView @@ -284,6 +289,14 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ [self.observer onFaviconChange:bitmap]; }; + m_web_view_bridge->on_finish_handling_key_event = [self](auto const& key_event) { + NSEvent* event = Ladybird::key_event_to_ns_event(key_event); + + self.event_being_redispatched = event; + [NSApp sendEvent:event]; + self.event_being_redispatched = nil; + }; + m_web_view_bridge->on_scroll = [self](auto position) { auto content_rect = [self frame]; auto document_rect = [[self documentView] frame]; @@ -1238,82 +1251,90 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_ - (void)mouseMoved:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::None); - m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::None); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)scrollWheel:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Middle); - CGFloat delta_x = -[event scrollingDeltaX]; - CGFloat delta_y = -[event scrollingDeltaY]; - if (![event hasPreciseScrollingDeltas]) { - delta_x *= [self scrollView].horizontalLineScroll; - delta_y *= [self scrollView].verticalLineScroll; - } - m_web_view_bridge->mouse_wheel_event(position, screen_position, button, modifiers, delta_x, delta_y); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseWheel, event, self, [self scrollView], GUI::MouseButton::Middle); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)mouseDown:(NSEvent*)event { [[self window] makeFirstResponder:self]; - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary); - - if (event.clickCount % 2 == 0) { - m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers); - } else { - m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers); - } + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], GUI::MouseButton::Primary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)mouseUp:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary); - m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], GUI::MouseButton::Primary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)mouseDragged:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Primary); - m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::Primary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)rightMouseDown:(NSEvent*)event { [[self window] makeFirstResponder:self]; - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary); - - if (event.clickCount % 2 == 0) { - m_web_view_bridge->mouse_double_click_event(position, screen_position, button, modifiers); - } else { - m_web_view_bridge->mouse_down_event(position, screen_position, button, modifiers); - } + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseDown, event, self, [self scrollView], GUI::MouseButton::Primary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)rightMouseUp:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary); - m_web_view_bridge->mouse_up_event(position, screen_position, button, modifiers); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseUp, event, self, [self scrollView], GUI::MouseButton::Secondary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); } - (void)rightMouseDragged:(NSEvent*)event { - auto [position, screen_position, button, modifiers] = Ladybird::ns_event_to_mouse_event(event, self, GUI::MouseButton::Secondary); - m_web_view_bridge->mouse_move_event(position, screen_position, button, modifiers); + auto mouse_event = Ladybird::ns_event_to_mouse_event(Web::MouseEvent::Type::MouseMove, event, self, [self scrollView], GUI::MouseButton::Secondary); + m_web_view_bridge->enqueue_input_event(move(mouse_event)); +} + +- (BOOL)performKeyEquivalent:(NSEvent*)event +{ + if ([event window] != [self window]) { + return NO; + } + if ([[self window] firstResponder] != self) { + return NO; + } + if (self.event_being_redispatched == event) { + return NO; + } + + [self keyDown:event]; + return YES; } - (void)keyDown:(NSEvent*)event { - auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event); - m_web_view_bridge->key_down_event(key_code, modifiers, code_point); + if (self.event_being_redispatched == event) { + return; + } + + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyDown, event); + m_web_view_bridge->enqueue_input_event(move(key_event)); } - (void)keyUp:(NSEvent*)event { - auto [key_code, modifiers, code_point] = Ladybird::ns_event_to_key_event(event); - m_web_view_bridge->key_up_event(key_code, modifiers, code_point); + if (self.event_being_redispatched == event) { + return; + } + + auto key_event = Ladybird::ns_event_to_key_event(Web::KeyEvent::Type::KeyUp, event); + m_web_view_bridge->enqueue_input_event(move(key_event)); } @end diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp index c29ed6058a..2797d65302 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.cpp @@ -95,39 +95,16 @@ void WebViewBridge::set_preferred_color_scheme(Web::CSS::PreferredColorScheme co client().async_set_preferred_color_scheme(m_client_state.page_index, color_scheme); } -void WebViewBridge::mouse_down_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers) +void WebViewBridge::enqueue_input_event(Web::MouseEvent event) { - client().async_mouse_down(m_client_state.page_index, to_content_position(position).to_type(), to_content_position(screen_position).to_type(), to_underlying(button), to_underlying(button), modifiers); + 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::mouse_up_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers) +void WebViewBridge::enqueue_input_event(Web::KeyEvent event) { - client().async_mouse_up(m_client_state.page_index, to_content_position(position).to_type(), to_content_position(screen_position).to_type(), to_underlying(button), to_underlying(button), modifiers); -} - -void WebViewBridge::mouse_move_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers) -{ - client().async_mouse_move(m_client_state.page_index, to_content_position(position).to_type(), to_content_position(screen_position).to_type(), 0, to_underlying(button), modifiers); -} - -void WebViewBridge::mouse_wheel_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers, int wheel_delta_x, int wheel_delta_y) -{ - client().async_mouse_wheel(m_client_state.page_index, to_content_position(position).to_type(), to_content_position(screen_position).to_type(), to_underlying(button), to_underlying(button), modifiers, wheel_delta_x, wheel_delta_y); -} - -void WebViewBridge::mouse_double_click_event(Gfx::IntPoint position, Gfx::IntPoint screen_position, GUI::MouseButton button, KeyModifier modifiers) -{ - client().async_doubleclick(m_client_state.page_index, to_content_position(position).to_type(), to_content_position(screen_position).to_type(), button, to_underlying(button), modifiers); -} - -void WebViewBridge::key_down_event(KeyCode key_code, KeyModifier modifiers, u32 code_point) -{ - client().async_key_down(m_client_state.page_index, key_code, modifiers, code_point); -} - -void WebViewBridge::key_up_event(KeyCode key_code, KeyModifier modifiers, u32 code_point) -{ - client().async_key_up(m_client_state.page_index, key_code, modifiers, code_point); + ViewImplementation::enqueue_input_event(move(event)); } Optional WebViewBridge::paintable() diff --git a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h index c64c79bee1..46f0125d0d 100644 --- a/Ladybird/AppKit/UI/LadybirdWebViewBridge.h +++ b/Ladybird/AppKit/UI/LadybirdWebViewBridge.h @@ -13,12 +13,9 @@ #include #include #include +#include #include -// FIXME: These should not be included outside of Serenity. -#include -#include - namespace Ladybird { class WebViewBridge final : public WebView::ViewImplementation { @@ -41,14 +38,8 @@ public: void update_palette(); void set_preferred_color_scheme(Web::CSS::PreferredColorScheme); - void mouse_down_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier); - void mouse_up_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier); - void mouse_move_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier); - void mouse_wheel_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier, int, int); - void mouse_double_click_event(Gfx::IntPoint, Gfx::IntPoint, GUI::MouseButton, KeyModifier); - - void key_down_event(KeyCode, KeyModifier, u32); - void key_up_event(KeyCode, KeyModifier, u32); + void enqueue_input_event(Web::MouseEvent); + void enqueue_input_event(Web::KeyEvent); struct Paintable { Gfx::Bitmap& bitmap;