From a64b679741562531a28372c1ae0fbe2d97487be8 Mon Sep 17 00:00:00 2001 From: "Ngo Iok Ui (Wu Yu Wei)" Date: Thu, 4 Jul 2024 12:23:03 +0900 Subject: [PATCH] Add basic keyboard input (#44) * Add keyboard module * Add basic keyboard handling * Update link to slides * Test self hosted runner * Add temp workaround to resize webview * Revert CI runner * chore: remove unused import * feat(keyboard): add character input * chore: remove todo * replace the url for testing keyborad for now * feat(clipboard): handle clipboard message * Revert "feat(clipboard): handle clipboard message" This reverts commit cf357f8b68dd37caea04cae92469ab82be5b676f. --------- Co-authored-by: Jason Tsai --- src/keyboard.rs | 269 ++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + src/window.rs | 13 ++- 3 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 src/keyboard.rs diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 00000000..935208f7 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,269 @@ +use log::info; +use servo::keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, Modifiers}; +use winit::event::{ElementState, KeyEvent}; +use winit::keyboard::{Key as LogicalKey, KeyCode, ModifiersState, NamedKey, PhysicalKey}; + +/// Some shortcuts use Cmd on Mac and Control on other systems. +#[cfg(macos)] +pub const CMD_OR_CONTROL: Modifiers = Modifiers::META; +/// Some shortcuts use Cmd on Mac and Control on other systems. +#[cfg(not(macos))] +pub const CMD_OR_CONTROL: Modifiers = Modifiers::CONTROL; + +/// Some shortcuts use Cmd on Mac and Alt on other systems. +#[cfg(macos)] +pub const CMD_OR_ALT: Modifiers = Modifiers::META; +/// Some shortcuts use Cmd on Mac and Alt on other systems. +#[cfg(not(macos))] +pub const CMD_OR_ALT: Modifiers = Modifiers::ALT; + +fn get_servo_key_from_winit_key(key: &LogicalKey) -> Key { + // TODO: figure out how to map NavigateForward, NavigateBackward + // TODO: map the remaining keys if possible + match key { + LogicalKey::Character(c) => return Key::Character(c.to_string()), + // printable: Key1 to Key0 + // printable: A to Z + LogicalKey::Named(NamedKey::Escape) => Key::Escape, + LogicalKey::Named(NamedKey::F1) => Key::F1, + LogicalKey::Named(NamedKey::F2) => Key::F2, + LogicalKey::Named(NamedKey::F3) => Key::F3, + LogicalKey::Named(NamedKey::F4) => Key::F4, + LogicalKey::Named(NamedKey::F5) => Key::F5, + LogicalKey::Named(NamedKey::F6) => Key::F6, + LogicalKey::Named(NamedKey::F7) => Key::F7, + LogicalKey::Named(NamedKey::F8) => Key::F8, + LogicalKey::Named(NamedKey::F9) => Key::F9, + LogicalKey::Named(NamedKey::F10) => Key::F10, + LogicalKey::Named(NamedKey::F11) => Key::F11, + LogicalKey::Named(NamedKey::F12) => Key::F12, + // F13 to F15 are not mapped + LogicalKey::Named(NamedKey::PrintScreen) => Key::PrintScreen, + // Scroll not mapped + LogicalKey::Named(NamedKey::Pause) => Key::Pause, + LogicalKey::Named(NamedKey::Insert) => Key::Insert, + LogicalKey::Named(NamedKey::Home) => Key::Home, + LogicalKey::Named(NamedKey::Delete) => Key::Delete, + LogicalKey::Named(NamedKey::End) => Key::End, + LogicalKey::Named(NamedKey::PageDown) => Key::PageDown, + LogicalKey::Named(NamedKey::PageUp) => Key::PageUp, + LogicalKey::Named(NamedKey::ArrowLeft) => Key::ArrowLeft, + LogicalKey::Named(NamedKey::ArrowUp) => Key::ArrowUp, + LogicalKey::Named(NamedKey::ArrowRight) => Key::ArrowRight, + LogicalKey::Named(NamedKey::ArrowDown) => Key::ArrowDown, + LogicalKey::Named(NamedKey::Backspace) => Key::Backspace, + LogicalKey::Named(NamedKey::Enter) => Key::Enter, + // printable: Space + LogicalKey::Named(NamedKey::Compose) => Key::Compose, + // Caret not mapped + LogicalKey::Named(NamedKey::NumLock) => Key::NumLock, + // printable: Numpad0 to Numpad9 + // AbntC1 and AbntC2 not mapped + // printable: Add, Apostrophe, + // Apps, At, Ax not mapped + // printable: Backslash, + LogicalKey::Named(NamedKey::LaunchApplication2) => Key::LaunchApplication2, + LogicalKey::Named(NamedKey::CapsLock) => Key::CapsLock, + // printable: Colon, Comma, + LogicalKey::Named(NamedKey::Convert) => Key::Convert, + // not mapped: Decimal, + // printable: Divide, Equals, Grave, + LogicalKey::Named(NamedKey::KanaMode) => Key::KanaMode, + LogicalKey::Named(NamedKey::KanjiMode) => Key::KanjiMode, + LogicalKey::Named(NamedKey::Alt) => Key::Alt, + // printable: LBracket, + LogicalKey::Named(NamedKey::Control) => Key::Control, + LogicalKey::Named(NamedKey::Shift) => Key::Shift, + LogicalKey::Named(NamedKey::Meta) => Key::Meta, + LogicalKey::Named(NamedKey::LaunchMail) => Key::LaunchMail, + // not mapped: MediaSelect, + LogicalKey::Named(NamedKey::MediaStop) => Key::MediaStop, + // printable: Minus, Multiply, + LogicalKey::Named(NamedKey::AudioVolumeMute) => Key::AudioVolumeMute, + LogicalKey::Named(NamedKey::LaunchApplication1) => Key::LaunchApplication1, + // not mapped: NavigateForward, NavigateBackward + LogicalKey::Named(NamedKey::MediaTrackNext) => Key::MediaTrackNext, + LogicalKey::Named(NamedKey::NonConvert) => Key::NonConvert, + // printable: NumpadComma, NumpadEnter, NumpadEquals, + // not mapped: OEM102, + // printable: Period, + LogicalKey::Named(NamedKey::MediaPlayPause) => Key::MediaPlayPause, + LogicalKey::Named(NamedKey::Power) => Key::Power, + LogicalKey::Named(NamedKey::MediaTrackPrevious) => Key::MediaTrackPrevious, + // printable RBracket + // printable Semicolon, Slash + LogicalKey::Named(NamedKey::Standby) => Key::Standby, + // not mapped: Stop, + // printable Subtract, + // not mapped: Sysrq, + LogicalKey::Named(NamedKey::Tab) => Key::Tab, + // printable: Underline, + // not mapped: Unlabeled, + LogicalKey::Named(NamedKey::AudioVolumeDown) => Key::AudioVolumeDown, + LogicalKey::Named(NamedKey::AudioVolumeUp) => Key::AudioVolumeUp, + LogicalKey::Named(NamedKey::WakeUp) => Key::WakeUp, + LogicalKey::Named(NamedKey::BrowserBack) => Key::BrowserBack, + LogicalKey::Named(NamedKey::BrowserFavorites) => Key::BrowserFavorites, + LogicalKey::Named(NamedKey::BrowserForward) => Key::BrowserForward, + LogicalKey::Named(NamedKey::BrowserHome) => Key::BrowserHome, + LogicalKey::Named(NamedKey::BrowserRefresh) => Key::BrowserRefresh, + LogicalKey::Named(NamedKey::BrowserSearch) => Key::BrowserSearch, + LogicalKey::Named(NamedKey::BrowserStop) => Key::BrowserStop, + // printable Yen, + LogicalKey::Named(NamedKey::Copy) => Key::Copy, + LogicalKey::Named(NamedKey::Paste) => Key::Paste, + LogicalKey::Named(NamedKey::Cut) => Key::Cut, + _ => Key::Unidentified, + } +} + +fn get_servo_location_from_physical_key(physical_key: PhysicalKey) -> Location { + let key_code = if let PhysicalKey::Code(key_code) = physical_key { + key_code + } else { + return Location::Standard; + }; + + // TODO: add more numpad keys + match key_code { + KeyCode::ShiftLeft | KeyCode::ControlLeft | KeyCode::AltLeft | KeyCode::SuperLeft => { + Location::Left + } + KeyCode::ShiftRight | KeyCode::ControlRight | KeyCode::AltRight | KeyCode::SuperRight => { + Location::Right + } + KeyCode::Numpad0 + | KeyCode::Numpad1 + | KeyCode::Numpad2 + | KeyCode::Numpad3 + | KeyCode::Numpad4 + | KeyCode::Numpad5 + | KeyCode::Numpad6 + | KeyCode::Numpad7 + | KeyCode::Numpad8 + | KeyCode::Numpad9 => Location::Numpad, + KeyCode::NumpadComma | KeyCode::NumpadEnter | KeyCode::NumpadEqual => Location::Numpad, + _ => Location::Standard, + } +} + +fn get_servo_code_from_physical_key(physical_key: PhysicalKey) -> Code { + let key_code = if let PhysicalKey::Code(key_code) = physical_key { + key_code + } else { + return Code::Unidentified; + }; + + // TODO: Map more codes + match key_code { + KeyCode::Escape => Code::Escape, + KeyCode::Digit1 => Code::Digit1, + KeyCode::Digit2 => Code::Digit2, + KeyCode::Digit3 => Code::Digit3, + KeyCode::Digit4 => Code::Digit4, + KeyCode::Digit5 => Code::Digit5, + KeyCode::Digit6 => Code::Digit6, + KeyCode::Digit7 => Code::Digit7, + KeyCode::Digit8 => Code::Digit8, + KeyCode::Digit9 => Code::Digit9, + KeyCode::Digit0 => Code::Digit0, + + KeyCode::Backspace => Code::Backspace, + KeyCode::Tab => Code::Tab, + KeyCode::KeyQ => Code::KeyQ, + KeyCode::KeyW => Code::KeyW, + KeyCode::KeyE => Code::KeyE, + KeyCode::KeyR => Code::KeyR, + KeyCode::KeyT => Code::KeyT, + KeyCode::KeyY => Code::KeyY, + KeyCode::KeyU => Code::KeyU, + KeyCode::KeyI => Code::KeyI, + KeyCode::KeyO => Code::KeyO, + KeyCode::KeyP => Code::KeyP, + KeyCode::BracketLeft => Code::BracketLeft, + KeyCode::BracketRight => Code::BracketRight, + KeyCode::Enter => Code::Enter, + + KeyCode::KeyA => Code::KeyA, + KeyCode::KeyS => Code::KeyS, + KeyCode::KeyD => Code::KeyD, + KeyCode::KeyF => Code::KeyF, + KeyCode::KeyG => Code::KeyG, + KeyCode::KeyH => Code::KeyH, + KeyCode::KeyJ => Code::KeyJ, + KeyCode::KeyK => Code::KeyK, + KeyCode::KeyL => Code::KeyL, + KeyCode::Semicolon => Code::Semicolon, + KeyCode::Quote => Code::Quote, + + KeyCode::ShiftLeft => Code::ShiftLeft, + KeyCode::Backslash => Code::Backslash, + KeyCode::KeyZ => Code::KeyZ, + KeyCode::KeyX => Code::KeyX, + KeyCode::KeyC => Code::KeyC, + KeyCode::KeyV => Code::KeyV, + KeyCode::KeyB => Code::KeyB, + KeyCode::KeyN => Code::KeyN, + KeyCode::KeyM => Code::KeyM, + KeyCode::Comma => Code::Comma, + KeyCode::Period => Code::Period, + KeyCode::Slash => Code::Slash, + KeyCode::ShiftRight => Code::ShiftRight, + + KeyCode::Space => Code::Space, + + KeyCode::F1 => Code::F1, + KeyCode::F2 => Code::F2, + KeyCode::F3 => Code::F3, + KeyCode::F4 => Code::F4, + KeyCode::F5 => Code::F5, + KeyCode::F6 => Code::F6, + KeyCode::F7 => Code::F7, + KeyCode::F8 => Code::F8, + KeyCode::F9 => Code::F9, + KeyCode::F10 => Code::F10, + + KeyCode::F11 => Code::F11, + KeyCode::F12 => Code::F12, + + KeyCode::ArrowUp => Code::ArrowUp, + KeyCode::PageUp => Code::PageUp, + KeyCode::ArrowLeft => Code::ArrowLeft, + KeyCode::ArrowRight => Code::ArrowRight, + + KeyCode::Home => Code::Home, + KeyCode::End => Code::End, + KeyCode::ArrowDown => Code::ArrowDown, + KeyCode::PageDown => Code::PageDown, + KeyCode::Insert => Code::Insert, + KeyCode::Delete => Code::Delete, + + _ => Code::Unidentified, + } +} + +fn get_modifiers(mods: ModifiersState) -> Modifiers { + let mut modifiers = Modifiers::empty(); + modifiers.set(Modifiers::CONTROL, mods.control_key()); + modifiers.set(Modifiers::SHIFT, mods.shift_key()); + modifiers.set(Modifiers::ALT, mods.alt_key()); + modifiers.set(Modifiers::META, mods.super_key()); + modifiers +} + +/// Convert Winit's KeyEvent to Servo's KeyboardEvent +pub fn keyboard_event_from_winit(input: &KeyEvent, state: ModifiersState) -> KeyboardEvent { + info!("winit keyboard input: {:?}", input); + KeyboardEvent { + state: match input.state { + ElementState::Pressed => KeyState::Down, + ElementState::Released => KeyState::Up, + }, + key: get_servo_key_from_winit_key(&input.logical_key), + code: get_servo_code_from_physical_key(input.physical_key), + location: get_servo_location_from_physical_key(input.physical_key), + modifiers: get_modifiers(state), + repeat: false, + is_composing: false, + } +} diff --git a/src/lib.rs b/src/lib.rs index d3b07459..5f152392 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,8 @@ pub mod app; /// Error and result types. pub mod errors; +/// Utilities to handle keyboard inputs and states. +pub mod keyboard; /// Utilities to read options and preferences. pub mod prefs; /// Utilities to access resource files diff --git a/src/window.rs b/src/window.rs index b8b5bca2..c3161006 100644 --- a/src/window.rs +++ b/src/window.rs @@ -23,10 +23,12 @@ use surfman::{Connection, GLApi, SurfaceType}; use winit::{ dpi::PhysicalPosition, event::{ElementState, TouchPhase, WindowEvent}, + keyboard::ModifiersState, window::{CursorIcon, Window as WinitWindow}, }; use crate::{ + keyboard::keyboard_event_from_winit, webview::{Panel, WebView}, Status, }; @@ -43,6 +45,7 @@ pub struct Window { webrender_gl: Rc, /// The mouse physical position in the web view. mouse_position: Cell>, + modifiers_state: Cell, } impl Window { @@ -77,6 +80,7 @@ impl Window { webview: None, webrender_gl, mouse_position: Cell::new(PhysicalPosition::default()), + modifiers_state: Cell::new(ModifiersState::default()), } } @@ -197,6 +201,12 @@ impl Window { WindowEvent::CloseRequested => { events.push(EmbedderEvent::Quit); } + WindowEvent::ModifiersChanged(modifier) => self.modifiers_state.set(modifier.state()), + WindowEvent::KeyboardInput { event, .. } => { + let event = keyboard_event_from_winit(&event, self.modifiers_state.get()); + log::trace!("Verso is handling {:?}", event); + events.push(EmbedderEvent::Keyboard(event)); + } e => log::warn!("Verso Window isn't supporting this window event yet: {e:?}"), } } @@ -220,7 +230,8 @@ impl Window { } EmbedderMsg::LoadComplete => { need_present = true; - let demo_url = ServoUrl::parse("https://demo.versotile.org").unwrap(); + // let demo_url = ServoUrl::parse("https://demo.versotile.org").unwrap(); + let demo_url = ServoUrl::parse("https://keyboard-test.space").unwrap(); let demo_id = TopLevelBrowsingContextId::new(); events.push(EmbedderEvent::NewWebView(demo_url, demo_id)); }