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.
This commit is contained in:
Timothy Flynn 2024-03-05 16:11:22 -05:00 committed by Andreas Kling
parent c1476c3405
commit 2c31ef11bc
Notes: sideshowbarker 2024-07-17 03:35:16 +09:00
5 changed files with 130 additions and 98 deletions

View file

@ -6,30 +6,17 @@
#pragma once
// FIXME: These should not be included outside of Serenity.
#include <Kernel/API/KeyCode.h>
#include <LibGUI/Event.h>
#include <LibWeb/Page/InputEvent.h>
#import <System/Cocoa.h>
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*);
}

View file

@ -36,13 +36,37 @@ static KeyModifier ns_modifiers_to_key_modifiers(NSEventModifierFlags modifier_f
return static_cast<KeyModifier>(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<Web::DevicePixels>();
auto screen_position = [NSEvent mouseLocation];
auto device_screen_position = ns_point_to_gfx_point(screen_position).to_type<Web::DevicePixels>();
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<int>(delta_x);
wheel_delta_y = static_cast<int>(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<KeyData>(event) };
}
NSEvent* key_event_to_ns_event(Web::KeyEvent const& event)
{
auto& chrome_data = verify_cast<KeyData>(*event.chrome_data);
return chrome_data.take_event();
}
}

View file

@ -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

View file

@ -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<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), to_underlying(button), to_underlying(button), modifiers);
event.position = to_content_position(event.position.to_type<int>()).to_type<Web::DevicePixels>();
event.screen_position = to_content_position(event.screen_position.to_type<int>()).to_type<Web::DevicePixels>();
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<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), 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<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), 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<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), 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<Web::DevicePixels>(), to_content_position(screen_position).to_type<Web::DevicePixels>(), 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> WebViewBridge::paintable()

View file

@ -13,12 +13,9 @@
#include <LibGfx/Size.h>
#include <LibGfx/StandardCursor.h>
#include <LibWeb/CSS/PreferredColorScheme.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWebView/ViewImplementation.h>
// FIXME: These should not be included outside of Serenity.
#include <Kernel/API/KeyCode.h>
#include <LibGUI/Event.h>
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;