From f3a8671bd5a308133794f403d3b82e29576af670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 9 May 2026 15:36:37 -0400 Subject: [PATCH] Add keyboard special key support for web content interaction Enter, Backspace, Tab, Escape, Delete, arrow keys, Home, End, PageUp, PageDown now forward to Servo. Previously only printable characters reached the web engine, making forms unusable. --- .../ely_app/src/shell/web_surface_keyboard.rs | 77 ++++++++++++++++--- crates/ely_servo_host/src/keyboard.rs | 23 +++++- crates/ely_servo_host/src/runtime_input.rs | 7 +- 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/crates/ely_app/src/shell/web_surface_keyboard.rs b/crates/ely_app/src/shell/web_surface_keyboard.rs index 0c9ea1f..ab8773f 100644 --- a/crates/ely_app/src/shell/web_surface_keyboard.rs +++ b/crates/ely_app/src/shell/web_surface_keyboard.rs @@ -10,15 +10,21 @@ impl ElyShell { _window: &mut Window, cx: &mut Context, ) { - let Some(text) = typed_text_from_key_down(event) else { - return; - }; let Some((tab_id, requested_url)) = self.active_external_tab_target() else { return; }; - if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) { - cx.stop_propagation(); + if let Some(text) = printable_text_from_key_down(event) { + if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) { + cx.stop_propagation(); + } + return; + } + + if let Some(text) = special_key_text(event) { + if self.type_text_in_external_web_viewport(tab_id, requested_url, text, cx) { + cx.stop_propagation(); + } } } @@ -36,7 +42,7 @@ impl ElyShell { } } -fn typed_text_from_key_down(event: &KeyDownEvent) -> Option<&str> { +fn printable_text_from_key_down(event: &KeyDownEvent) -> Option<&str> { let modifiers = &event.keystroke.modifiers; if modifiers.control || modifiers.platform || modifiers.function { return None; @@ -52,17 +58,39 @@ fn typed_text_from_key_down(event: &KeyDownEvent) -> Option<&str> { Some(text) } +fn special_key_text(event: &KeyDownEvent) -> Option<&'static str> { + if event.keystroke.modifiers.platform || event.keystroke.modifiers.control { + return None; + } + match event.keystroke.key.as_str() { + "enter" => Some("\n"), + "backspace" => Some("\x08"), + "tab" => Some("\t"), + "escape" => Some("\x1b"), + "delete" => Some("\x7f"), + "left" => Some("\u{F702}"), + "right" => Some("\u{F703}"), + "up" => Some("\u{F700}"), + "down" => Some("\u{F701}"), + "home" => Some("\u{F729}"), + "end" => Some("\u{F72B}"), + "pageup" => Some("\u{F72C}"), + "pagedown" => Some("\u{F72D}"), + _ => None, + } +} + #[cfg(test)] mod tests { use gpui::{KeyDownEvent, Keystroke, Modifiers}; - use super::typed_text_from_key_down; + use super::{printable_text_from_key_down, special_key_text}; #[test] fn typed_text_uses_printable_key_char() { let event = key_down("e", Some("e"), Modifiers::none()); - assert_eq!(typed_text_from_key_down(&event), Some("e")); + assert_eq!(printable_text_from_key_down(&event), Some("e")); } #[test] @@ -71,7 +99,7 @@ mod tests { modifiers.shift = true; let event = key_down("1", Some("!"), modifiers); - assert_eq!(typed_text_from_key_down(&event), Some("!")); + assert_eq!(printable_text_from_key_down(&event), Some("!")); } #[test] @@ -80,7 +108,36 @@ mod tests { modifiers.platform = true; let event = key_down("l", None, modifiers); - assert_eq!(typed_text_from_key_down(&event), None); + assert_eq!(printable_text_from_key_down(&event), None); + } + + #[test] + fn special_key_enter() { + let event = key_down("enter", None, Modifiers::none()); + assert_eq!(special_key_text(&event), Some("\n")); + } + + #[test] + fn special_key_backspace() { + let event = key_down("backspace", None, Modifiers::none()); + assert_eq!(special_key_text(&event), Some("\x08")); + } + + #[test] + fn special_key_ignored_with_platform_modifier() { + let mut modifiers = Modifiers::none(); + modifiers.platform = true; + let event = key_down("enter", None, modifiers); + assert_eq!(special_key_text(&event), None); + } + + #[test] + fn special_key_arrows() { + let event = key_down("left", None, Modifiers::none()); + assert_eq!(special_key_text(&event), Some("\u{F702}")); + + let event = key_down("right", None, Modifiers::none()); + assert_eq!(special_key_text(&event), Some("\u{F703}")); } fn key_down(key: &str, key_char: Option<&str>, modifiers: Modifiers) -> KeyDownEvent { diff --git a/crates/ely_servo_host/src/keyboard.rs b/crates/ely_servo_host/src/keyboard.rs index 627f744..2c33546 100644 --- a/crates/ely_servo_host/src/keyboard.rs +++ b/crates/ely_servo_host/src/keyboard.rs @@ -1,6 +1,25 @@ -use servo::Code; +use servo::{Code, Key}; -pub(crate) fn keyboard_code_for_character(character: char) -> Code { +pub(crate) fn key_and_code_for_character(character: char) -> (Key, Code) { + match character { + '\n' | '\r' => (Key::Enter, Code::Enter), + '\x08' => (Key::Backspace, Code::Backspace), + '\t' => (Key::Tab, Code::Tab), + '\x1b' => (Key::Escape, Code::Escape), + '\x7f' => (Key::Delete, Code::Delete), + '\u{F700}' => (Key::ArrowUp, Code::ArrowUp), + '\u{F701}' => (Key::ArrowDown, Code::ArrowDown), + '\u{F702}' => (Key::ArrowLeft, Code::ArrowLeft), + '\u{F703}' => (Key::ArrowRight, Code::ArrowRight), + '\u{F729}' => (Key::Home, Code::Home), + '\u{F72B}' => (Key::End, Code::End), + '\u{F72C}' => (Key::PageUp, Code::PageUp), + '\u{F72D}' => (Key::PageDown, Code::PageDown), + _ => (Key::Character(character.to_string()), printable_code(character)), + } +} + +fn printable_code(character: char) -> Code { match character { 'a' | 'A' => Code::KeyA, 'b' | 'B' => Code::KeyB, diff --git a/crates/ely_servo_host/src/runtime_input.rs b/crates/ely_servo_host/src/runtime_input.rs index 0231cd9..f19f6e3 100644 --- a/crates/ely_servo_host/src/runtime_input.rs +++ b/crates/ely_servo_host/src/runtime_input.rs @@ -1,10 +1,10 @@ use servo::{ - DevicePoint, InputEvent, Key, KeyState, KeyboardEvent, Location, Modifiers, MouseButton, + DevicePoint, InputEvent, KeyState, KeyboardEvent, Location, Modifiers, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, TouchEvent, TouchEventType, TouchId, WebView, WebViewPoint, }; -use crate::keyboard::keyboard_code_for_character; +use crate::keyboard::key_and_code_for_character; pub(super) fn send_mouse_click(webview: &WebView, x: u32, y: u32) { let point = point(x, y); @@ -32,8 +32,7 @@ pub(super) fn send_touch_tap(webview: &WebView, x: u32, y: u32) { pub(super) fn send_keyboard_text(webview: &WebView, text: &str) { for character in text.chars() { - let key = Key::Character(character.to_string()); - let code = keyboard_code_for_character(character); + let (key, code) = key_and_code_for_character(character); webview.notify_input_event(InputEvent::Keyboard(KeyboardEvent::new_without_event( KeyState::Down, key.clone(),