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:
parent
c1476c3405
commit
2c31ef11bc
Notes:
sideshowbarker
2024-07-17 03:35:16 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/2c31ef11bc Pull-request: https://github.com/SerenityOS/serenity/pull/23483
5 changed files with 130 additions and 98 deletions
|
@ -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*);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue