diff --git a/Cargo.toml b/Cargo.toml index 8028f0ed..8ff794a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,8 +67,8 @@ simple_logger = { version = "2.1.0", default_features = false } softbuffer = "0.3.0" [target.'cfg(target_os = "android")'.dependencies] -# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995 -android-activity = "0.4.0" +# Coordinate the next winit release android-activity 0.5 release +android-activity = { git = "https://github.com/rust-mobile/android-activity" } ndk = "0.7.0" ndk-sys = "0.4.0" diff --git a/src/platform_impl/android/keycodes.rs b/src/platform_impl/android/keycodes.rs index 8933e796..c819070d 100644 --- a/src/platform_impl/android/keycodes.rs +++ b/src/platform_impl/android/keycodes.rs @@ -1,4 +1,7 @@ -use android_activity::input::Keycode; +use android_activity::{ + input::{KeyAction, KeyEvent, KeyMapChar, Keycode}, + AndroidApp, +}; use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKey, NativeKeyCode}; @@ -156,324 +159,405 @@ pub fn to_physical_keycode(keycode: Keycode) -> KeyCode { } } -// TODO: We need to expose getUnicodeChar via android-activity instead of having -// a fixed mapping from key codes -pub fn to_logical(keycode: Keycode, native: NativeKey) -> Key { +/// Tries to map the `key_event` to a `KeyMapChar` containing a unicode character or dead key accent +/// +/// This takes a `KeyEvent` and looks up its corresponding `KeyCharacterMap` and +/// uses that to try and map the `key_code` + `meta_state` to a unicode +/// character or a dead key that can be combined with the next key press. +pub fn character_map_and_combine_key( + app: &AndroidApp, + key_event: &KeyEvent<'_>, + combining_accent: &mut Option, +) -> Option { + let device_id = key_event.device_id(); + + let key_map = match app.device_key_character_map(device_id) { + Ok(key_map) => key_map, + Err(err) => { + log::warn!("Failed to look up `KeyCharacterMap` for device {device_id}: {err:?}"); + return None; + } + }; + + match key_map.get(key_event.key_code(), key_event.meta_state()) { + Ok(KeyMapChar::Unicode(unicode)) => { + // Only do dead key combining on key down + if key_event.action() == KeyAction::Down { + let combined_unicode = if let Some(accent) = combining_accent { + match key_map.get_dead_char(*accent, unicode) { + Ok(Some(key)) => Some(key), + Ok(None) => None, + Err(err) => { + log::warn!("KeyEvent: Failed to combine 'dead key' accent '{accent}' with '{unicode}': {err:?}"); + None + } + } + } else { + Some(unicode) + }; + *combining_accent = None; + combined_unicode.map(KeyMapChar::Unicode) + } else { + Some(KeyMapChar::Unicode(unicode)) + } + } + Ok(KeyMapChar::CombiningAccent(accent)) => { + if key_event.action() == KeyAction::Down { + *combining_accent = Some(accent); + } + Some(KeyMapChar::CombiningAccent(accent)) + } + Ok(KeyMapChar::None) => { + // Leave any combining_accent state in tact (seems to match how other + // Android apps work) + None + } + Err(err) => { + log::warn!("KeyEvent: Failed to get key map character: {err:?}"); + *combining_accent = None; + None + } + } +} + +pub fn to_logical(key_char: Option, keycode: Keycode) -> Key { use android_activity::input::Keycode::*; - match keycode { - Unknown => Key::Unidentified(native), + let native = NativeKey::Android(keycode.into()); - // Can be added on demand - SoftLeft => Key::Unidentified(native), - SoftRight => Key::Unidentified(native), + match key_char { + Some(KeyMapChar::Unicode(c)) => { + Key::Character(smol_str::SmolStr::from_iter([c].into_iter())) + } + Some(KeyMapChar::CombiningAccent(c)) => Key::Dead(Some(c)), + None | Some(KeyMapChar::None) => match keycode { + // Using `BrowserHome` instead of `GoHome` according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + Home => Key::BrowserHome, + Back => Key::BrowserBack, + Call => Key::Call, + Endcall => Key::EndCall, - // Using `BrowserHome` instead of `GoHome` according to - // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values - Home => Key::BrowserHome, - Back => Key::BrowserBack, - Call => Key::Call, - Endcall => Key::EndCall, + //------------------------------------------------------------------------------- + // These should be redundant because they should have already been matched + // as `KeyMapChar::Unicode`, but also matched here as a fallback + Keycode0 => Key::Character("0".into()), + Keycode1 => Key::Character("1".into()), + Keycode2 => Key::Character("2".into()), + Keycode3 => Key::Character("3".into()), + Keycode4 => Key::Character("4".into()), + Keycode5 => Key::Character("5".into()), + Keycode6 => Key::Character("6".into()), + Keycode7 => Key::Character("7".into()), + Keycode8 => Key::Character("8".into()), + Keycode9 => Key::Character("9".into()), + Star => Key::Character("*".into()), + Pound => Key::Character("#".into()), + A => Key::Character("a".into()), + B => Key::Character("b".into()), + C => Key::Character("c".into()), + D => Key::Character("d".into()), + E => Key::Character("e".into()), + F => Key::Character("f".into()), + G => Key::Character("g".into()), + H => Key::Character("h".into()), + I => Key::Character("i".into()), + J => Key::Character("j".into()), + K => Key::Character("k".into()), + L => Key::Character("l".into()), + M => Key::Character("m".into()), + N => Key::Character("n".into()), + O => Key::Character("o".into()), + P => Key::Character("p".into()), + Q => Key::Character("q".into()), + R => Key::Character("r".into()), + S => Key::Character("s".into()), + T => Key::Character("t".into()), + U => Key::Character("u".into()), + V => Key::Character("v".into()), + W => Key::Character("w".into()), + X => Key::Character("x".into()), + Y => Key::Character("y".into()), + Z => Key::Character("z".into()), + Comma => Key::Character(",".into()), + Period => Key::Character(".".into()), + Grave => Key::Character("`".into()), + Minus => Key::Character("-".into()), + Equals => Key::Character("=".into()), + LeftBracket => Key::Character("[".into()), + RightBracket => Key::Character("]".into()), + Backslash => Key::Character("\\".into()), + Semicolon => Key::Character(";".into()), + Apostrophe => Key::Character("'".into()), + Slash => Key::Character("/".into()), + At => Key::Character("@".into()), + Plus => Key::Character("+".into()), + //------------------------------------------------------------------------------- + DpadUp => Key::ArrowUp, + DpadDown => Key::ArrowDown, + DpadLeft => Key::ArrowLeft, + DpadRight => Key::ArrowRight, + DpadCenter => Key::Enter, - //------------------------------------------------------------------------------- - // Reporting unidentified, because the specific character is layout dependent. - // (I'm not sure though) - Keycode0 => Key::Character("0".into()), - Keycode1 => Key::Character("1".into()), - Keycode2 => Key::Character("2".into()), - Keycode3 => Key::Character("3".into()), - Keycode4 => Key::Character("4".into()), - Keycode5 => Key::Character("5".into()), - Keycode6 => Key::Character("6".into()), - Keycode7 => Key::Character("7".into()), - Keycode8 => Key::Character("8".into()), - Keycode9 => Key::Character("9".into()), - Star => Key::Character("*".into()), - Pound => Key::Character("#".into()), - A => Key::Character("a".into()), - B => Key::Character("b".into()), - C => Key::Character("c".into()), - D => Key::Character("d".into()), - E => Key::Character("e".into()), - F => Key::Character("f".into()), - G => Key::Character("g".into()), - H => Key::Character("h".into()), - I => Key::Character("i".into()), - J => Key::Character("j".into()), - K => Key::Character("k".into()), - L => Key::Character("l".into()), - M => Key::Character("m".into()), - N => Key::Character("n".into()), - O => Key::Character("o".into()), - P => Key::Character("p".into()), - Q => Key::Character("q".into()), - R => Key::Character("r".into()), - S => Key::Character("s".into()), - T => Key::Character("t".into()), - U => Key::Character("u".into()), - V => Key::Character("v".into()), - W => Key::Character("w".into()), - X => Key::Character("x".into()), - Y => Key::Character("y".into()), - Z => Key::Character("z".into()), - Comma => Key::Character(",".into()), - Period => Key::Character(".".into()), - Grave => Key::Character("`".into()), - Minus => Key::Character("-".into()), - Equals => Key::Character("=".into()), - LeftBracket => Key::Character("[".into()), - RightBracket => Key::Character("]".into()), - Backslash => Key::Character("\\".into()), - Semicolon => Key::Character(";".into()), - Apostrophe => Key::Character("'".into()), - Slash => Key::Character("/".into()), - At => Key::Character("@".into()), - Plus => Key::Character("+".into()), - //------------------------------------------------------------------------------- - DpadUp => Key::ArrowUp, - DpadDown => Key::ArrowDown, - DpadLeft => Key::ArrowLeft, - DpadRight => Key::ArrowRight, - DpadCenter => Key::Enter, + VolumeUp => Key::AudioVolumeUp, + VolumeDown => Key::AudioVolumeDown, + Power => Key::Power, + Camera => Key::Camera, + Clear => Key::Clear, - VolumeUp => Key::AudioVolumeUp, - VolumeDown => Key::AudioVolumeDown, - Power => Key::Power, - Camera => Key::Camera, - Clear => Key::Clear, + AltLeft => Key::Alt, + AltRight => Key::Alt, + ShiftLeft => Key::Shift, + ShiftRight => Key::Shift, + Tab => Key::Tab, + Space => Key::Space, + Sym => Key::Symbol, + Explorer => Key::LaunchWebBrowser, + Envelope => Key::LaunchMail, + Enter => Key::Enter, + Del => Key::Backspace, - AltLeft => Key::Alt, - AltRight => Key::Alt, - ShiftLeft => Key::Shift, - ShiftRight => Key::Shift, - Tab => Key::Tab, - Space => Key::Space, - Sym => Key::Symbol, - Explorer => Key::LaunchWebBrowser, - Envelope => Key::LaunchMail, - Enter => Key::Enter, - Del => Key::Backspace, + // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM + Num => Key::Alt, - // According to https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_NUM - Num => Key::Alt, + Headsethook => Key::HeadsetHook, + Focus => Key::CameraFocus, - Headsethook => Key::HeadsetHook, - Focus => Key::CameraFocus, + Notification => Key::Notification, + Search => Key::BrowserSearch, + MediaPlayPause => Key::MediaPlayPause, + MediaStop => Key::MediaStop, + MediaNext => Key::MediaTrackNext, + MediaPrevious => Key::MediaTrackPrevious, + MediaRewind => Key::MediaRewind, + MediaFastForward => Key::MediaFastForward, + Mute => Key::MicrophoneVolumeMute, + PageUp => Key::PageUp, + PageDown => Key::PageDown, - Menu => Key::Unidentified(native), + Escape => Key::Escape, + ForwardDel => Key::Delete, + CtrlLeft => Key::Control, + CtrlRight => Key::Control, + CapsLock => Key::CapsLock, + ScrollLock => Key::ScrollLock, + MetaLeft => Key::Super, + MetaRight => Key::Super, + Function => Key::Fn, + Sysrq => Key::PrintScreen, + Break => Key::Pause, + MoveHome => Key::Home, + MoveEnd => Key::End, + Insert => Key::Insert, + Forward => Key::BrowserForward, + MediaPlay => Key::MediaPlay, + MediaPause => Key::MediaPause, + MediaClose => Key::MediaClose, + MediaEject => Key::Eject, + MediaRecord => Key::MediaRecord, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + NumLock => Key::NumLock, + Numpad0 => Key::Character("0".into()), + Numpad1 => Key::Character("1".into()), + Numpad2 => Key::Character("2".into()), + Numpad3 => Key::Character("3".into()), + Numpad4 => Key::Character("4".into()), + Numpad5 => Key::Character("5".into()), + Numpad6 => Key::Character("6".into()), + Numpad7 => Key::Character("7".into()), + Numpad8 => Key::Character("8".into()), + Numpad9 => Key::Character("9".into()), + NumpadDivide => Key::Character("/".into()), + NumpadMultiply => Key::Character("*".into()), + NumpadSubtract => Key::Character("-".into()), + NumpadAdd => Key::Character("+".into()), + NumpadDot => Key::Character(".".into()), + NumpadComma => Key::Character(",".into()), + NumpadEnter => Key::Enter, + NumpadEquals => Key::Character("=".into()), + NumpadLeftParen => Key::Character("(".into()), + NumpadRightParen => Key::Character(")".into()), - Notification => Key::Notification, - Search => Key::BrowserSearch, - MediaPlayPause => Key::MediaPlayPause, - MediaStop => Key::MediaStop, - MediaNext => Key::MediaTrackNext, - MediaPrevious => Key::MediaTrackPrevious, - MediaRewind => Key::MediaRewind, - MediaFastForward => Key::MediaFastForward, - Mute => Key::MicrophoneVolumeMute, - PageUp => Key::PageUp, - PageDown => Key::PageDown, - Pictsymbols => Key::Unidentified(native), - SwitchCharset => Key::Unidentified(native), + VolumeMute => Key::AudioVolumeMute, + Info => Key::Info, + ChannelUp => Key::ChannelUp, + ChannelDown => Key::ChannelDown, + ZoomIn => Key::ZoomIn, + ZoomOut => Key::ZoomOut, + Tv => Key::TV, + Guide => Key::Guide, + Dvr => Key::DVR, + Bookmark => Key::BrowserFavorites, + Captions => Key::ClosedCaptionToggle, + Settings => Key::Settings, + TvPower => Key::TVPower, + TvInput => Key::TVInput, + StbPower => Key::STBPower, + StbInput => Key::STBInput, + AvrPower => Key::AVRPower, + AvrInput => Key::AVRInput, + ProgRed => Key::ColorF0Red, + ProgGreen => Key::ColorF1Green, + ProgYellow => Key::ColorF2Yellow, + ProgBlue => Key::ColorF3Blue, + AppSwitch => Key::AppSwitch, + LanguageSwitch => Key::GroupNext, + MannerMode => Key::MannerMode, + Keycode3dMode => Key::TV3DMode, + Contacts => Key::LaunchContacts, + Calendar => Key::LaunchCalendar, + Music => Key::LaunchMusicPlayer, + Calculator => Key::LaunchApplication2, + ZenkakuHankaku => Key::ZenkakuHankaku, + Eisu => Key::Eisu, + Muhenkan => Key::NonConvert, + Henkan => Key::Convert, + KatakanaHiragana => Key::HiraganaKatakana, + Kana => Key::KanjiMode, + BrightnessDown => Key::BrightnessDown, + BrightnessUp => Key::BrightnessUp, + MediaAudioTrack => Key::MediaAudioTrack, + Sleep => Key::Standby, + Wakeup => Key::WakeUp, + Pairing => Key::Pairing, + MediaTopMenu => Key::MediaTopMenu, + LastChannel => Key::MediaLast, + TvDataService => Key::TVDataService, + VoiceAssist => Key::VoiceDial, + TvRadioService => Key::TVRadioService, + TvTeletext => Key::Teletext, + TvNumberEntry => Key::TVNumberEntry, + TvTerrestrialAnalog => Key::TVTerrestrialAnalog, + TvTerrestrialDigital => Key::TVTerrestrialDigital, + TvSatellite => Key::TVSatellite, + TvSatelliteBs => Key::TVSatelliteBS, + TvSatelliteCs => Key::TVSatelliteCS, + TvSatelliteService => Key::TVSatelliteToggle, + TvNetwork => Key::TVNetwork, + TvAntennaCable => Key::TVAntennaCable, + TvInputHdmi1 => Key::TVInputHDMI1, + TvInputHdmi2 => Key::TVInputHDMI2, + TvInputHdmi3 => Key::TVInputHDMI3, + TvInputHdmi4 => Key::TVInputHDMI4, + TvInputComposite1 => Key::TVInputComposite1, + TvInputComposite2 => Key::TVInputComposite2, + TvInputComponent1 => Key::TVInputComponent1, + TvInputComponent2 => Key::TVInputComponent2, + TvInputVga1 => Key::TVInputVGA1, + TvAudioDescription => Key::TVAudioDescription, + TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, + TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, + TvZoomMode => Key::ZoomToggle, + TvContentsMenu => Key::TVContentsMenu, + TvMediaContextMenu => Key::TVMediaContext, + TvTimerProgramming => Key::TVTimer, + Help => Key::Help, + NavigatePrevious => Key::NavigatePrevious, + NavigateNext => Key::NavigateNext, + NavigateIn => Key::NavigateIn, + NavigateOut => Key::NavigateOut, + MediaSkipForward => Key::MediaSkipForward, + MediaSkipBackward => Key::MediaSkipBackward, + MediaStepForward => Key::MediaStepForward, + MediaStepBackward => Key::MediaStepBackward, + Cut => Key::Cut, + Copy => Key::Copy, + Paste => Key::Paste, + Refresh => Key::BrowserRefresh, - // ----------------------------------------------------------------- - // Gamepad events should be exposed through a separate API, not - // keyboard events - ButtonA => Key::Unidentified(native), - ButtonB => Key::Unidentified(native), - ButtonC => Key::Unidentified(native), - ButtonX => Key::Unidentified(native), - ButtonY => Key::Unidentified(native), - ButtonZ => Key::Unidentified(native), - ButtonL1 => Key::Unidentified(native), - ButtonR1 => Key::Unidentified(native), - ButtonL2 => Key::Unidentified(native), - ButtonR2 => Key::Unidentified(native), - ButtonThumbl => Key::Unidentified(native), - ButtonThumbr => Key::Unidentified(native), - ButtonStart => Key::Unidentified(native), - ButtonSelect => Key::Unidentified(native), - ButtonMode => Key::Unidentified(native), - // ----------------------------------------------------------------- - Escape => Key::Escape, - ForwardDel => Key::Delete, - CtrlLeft => Key::Control, - CtrlRight => Key::Control, - CapsLock => Key::CapsLock, - ScrollLock => Key::ScrollLock, - MetaLeft => Key::Super, - MetaRight => Key::Super, - Function => Key::Fn, - Sysrq => Key::PrintScreen, - Break => Key::Pause, - MoveHome => Key::Home, - MoveEnd => Key::End, - Insert => Key::Insert, - Forward => Key::BrowserForward, - MediaPlay => Key::MediaPlay, - MediaPause => Key::MediaPause, - MediaClose => Key::MediaClose, - MediaEject => Key::Eject, - MediaRecord => Key::MediaRecord, - F1 => Key::F1, - F2 => Key::F2, - F3 => Key::F3, - F4 => Key::F4, - F5 => Key::F5, - F6 => Key::F6, - F7 => Key::F7, - F8 => Key::F8, - F9 => Key::F9, - F10 => Key::F10, - F11 => Key::F11, - F12 => Key::F12, - NumLock => Key::NumLock, - Numpad0 => Key::Character("0".into()), - Numpad1 => Key::Character("1".into()), - Numpad2 => Key::Character("2".into()), - Numpad3 => Key::Character("3".into()), - Numpad4 => Key::Character("4".into()), - Numpad5 => Key::Character("5".into()), - Numpad6 => Key::Character("6".into()), - Numpad7 => Key::Character("7".into()), - Numpad8 => Key::Character("8".into()), - Numpad9 => Key::Character("9".into()), - NumpadDivide => Key::Character("/".into()), - NumpadMultiply => Key::Character("*".into()), - NumpadSubtract => Key::Character("-".into()), - NumpadAdd => Key::Character("+".into()), - NumpadDot => Key::Character(".".into()), - NumpadComma => Key::Character(",".into()), - NumpadEnter => Key::Enter, - NumpadEquals => Key::Character("=".into()), - NumpadLeftParen => Key::Character("(".into()), - NumpadRightParen => Key::Character(")".into()), + // ----------------------------------------------------------------- + // Keycodes that don't have a logical Key mapping + // ----------------------------------------------------------------- + Unknown => Key::Unidentified(native), - VolumeMute => Key::AudioVolumeMute, - Info => Key::Info, - ChannelUp => Key::ChannelUp, - ChannelDown => Key::ChannelDown, - ZoomIn => Key::ZoomIn, - ZoomOut => Key::ZoomOut, - Tv => Key::TV, - Window => Key::Unidentified(native), - Guide => Key::Guide, - Dvr => Key::DVR, - Bookmark => Key::BrowserFavorites, - Captions => Key::ClosedCaptionToggle, - Settings => Key::Settings, - TvPower => Key::TVPower, - TvInput => Key::TVInput, - StbPower => Key::STBPower, - StbInput => Key::STBInput, - AvrPower => Key::AVRPower, - AvrInput => Key::AVRInput, - ProgRed => Key::ColorF0Red, - ProgGreen => Key::ColorF1Green, - ProgYellow => Key::ColorF2Yellow, - ProgBlue => Key::ColorF3Blue, - AppSwitch => Key::AppSwitch, - Button1 => Key::Unidentified(native), - Button2 => Key::Unidentified(native), - Button3 => Key::Unidentified(native), - Button4 => Key::Unidentified(native), - Button5 => Key::Unidentified(native), - Button6 => Key::Unidentified(native), - Button7 => Key::Unidentified(native), - Button8 => Key::Unidentified(native), - Button9 => Key::Unidentified(native), - Button10 => Key::Unidentified(native), - Button11 => Key::Unidentified(native), - Button12 => Key::Unidentified(native), - Button13 => Key::Unidentified(native), - Button14 => Key::Unidentified(native), - Button15 => Key::Unidentified(native), - Button16 => Key::Unidentified(native), - LanguageSwitch => Key::GroupNext, - MannerMode => Key::MannerMode, - Keycode3dMode => Key::TV3DMode, - Contacts => Key::LaunchContacts, - Calendar => Key::LaunchCalendar, - Music => Key::LaunchMusicPlayer, - Calculator => Key::LaunchApplication2, - ZenkakuHankaku => Key::ZenkakuHankaku, - Eisu => Key::Eisu, - Muhenkan => Key::NonConvert, - Henkan => Key::Convert, - KatakanaHiragana => Key::HiraganaKatakana, - Yen => Key::Unidentified(native), - Ro => Key::Unidentified(native), - Kana => Key::KanjiMode, - Assist => Key::Unidentified(native), - BrightnessDown => Key::BrightnessDown, - BrightnessUp => Key::BrightnessUp, - MediaAudioTrack => Key::MediaAudioTrack, - Sleep => Key::Standby, - Wakeup => Key::WakeUp, - Pairing => Key::Pairing, - MediaTopMenu => Key::MediaTopMenu, - Keycode11 => Key::Unidentified(native), - Keycode12 => Key::Unidentified(native), - LastChannel => Key::MediaLast, - TvDataService => Key::TVDataService, - VoiceAssist => Key::VoiceDial, - TvRadioService => Key::TVRadioService, - TvTeletext => Key::Teletext, - TvNumberEntry => Key::TVNumberEntry, - TvTerrestrialAnalog => Key::TVTerrestrialAnalog, - TvTerrestrialDigital => Key::TVTerrestrialDigital, - TvSatellite => Key::TVSatellite, - TvSatelliteBs => Key::TVSatelliteBS, - TvSatelliteCs => Key::TVSatelliteCS, - TvSatelliteService => Key::TVSatelliteToggle, - TvNetwork => Key::TVNetwork, - TvAntennaCable => Key::TVAntennaCable, - TvInputHdmi1 => Key::TVInputHDMI1, - TvInputHdmi2 => Key::TVInputHDMI2, - TvInputHdmi3 => Key::TVInputHDMI3, - TvInputHdmi4 => Key::TVInputHDMI4, - TvInputComposite1 => Key::TVInputComposite1, - TvInputComposite2 => Key::TVInputComposite2, - TvInputComponent1 => Key::TVInputComponent1, - TvInputComponent2 => Key::TVInputComponent2, - TvInputVga1 => Key::TVInputVGA1, - TvAudioDescription => Key::TVAudioDescription, - TvAudioDescriptionMixUp => Key::TVAudioDescriptionMixUp, - TvAudioDescriptionMixDown => Key::TVAudioDescriptionMixDown, - TvZoomMode => Key::ZoomToggle, - TvContentsMenu => Key::TVContentsMenu, - TvMediaContextMenu => Key::TVMediaContext, - TvTimerProgramming => Key::TVTimer, - Help => Key::Help, - NavigatePrevious => Key::NavigatePrevious, - NavigateNext => Key::NavigateNext, - NavigateIn => Key::NavigateIn, - NavigateOut => Key::NavigateOut, - StemPrimary => Key::Unidentified(native), - Stem1 => Key::Unidentified(native), - Stem2 => Key::Unidentified(native), - Stem3 => Key::Unidentified(native), - DpadUpLeft => Key::Unidentified(native), - DpadDownLeft => Key::Unidentified(native), - DpadUpRight => Key::Unidentified(native), - DpadDownRight => Key::Unidentified(native), - MediaSkipForward => Key::MediaSkipForward, - MediaSkipBackward => Key::MediaSkipBackward, - MediaStepForward => Key::MediaStepForward, - MediaStepBackward => Key::MediaStepBackward, - SoftSleep => Key::Unidentified(native), - Cut => Key::Cut, - Copy => Key::Copy, - Paste => Key::Paste, - SystemNavigationUp => Key::Unidentified(native), - SystemNavigationDown => Key::Unidentified(native), - SystemNavigationLeft => Key::Unidentified(native), - SystemNavigationRight => Key::Unidentified(native), - AllApps => Key::Unidentified(native), - Refresh => Key::BrowserRefresh, - ThumbsUp => Key::Unidentified(native), - ThumbsDown => Key::Unidentified(native), - ProfileSwitch => Key::Unidentified(native), + // Can be added on demand + SoftLeft => Key::Unidentified(native), + SoftRight => Key::Unidentified(native), + + Menu => Key::Unidentified(native), + + Pictsymbols => Key::Unidentified(native), + SwitchCharset => Key::Unidentified(native), + + // ----------------------------------------------------------------- + // Gamepad events should be exposed through a separate API, not + // keyboard events + ButtonA => Key::Unidentified(native), + ButtonB => Key::Unidentified(native), + ButtonC => Key::Unidentified(native), + ButtonX => Key::Unidentified(native), + ButtonY => Key::Unidentified(native), + ButtonZ => Key::Unidentified(native), + ButtonL1 => Key::Unidentified(native), + ButtonR1 => Key::Unidentified(native), + ButtonL2 => Key::Unidentified(native), + ButtonR2 => Key::Unidentified(native), + ButtonThumbl => Key::Unidentified(native), + ButtonThumbr => Key::Unidentified(native), + ButtonStart => Key::Unidentified(native), + ButtonSelect => Key::Unidentified(native), + ButtonMode => Key::Unidentified(native), + // ----------------------------------------------------------------- + Window => Key::Unidentified(native), + + Button1 => Key::Unidentified(native), + Button2 => Key::Unidentified(native), + Button3 => Key::Unidentified(native), + Button4 => Key::Unidentified(native), + Button5 => Key::Unidentified(native), + Button6 => Key::Unidentified(native), + Button7 => Key::Unidentified(native), + Button8 => Key::Unidentified(native), + Button9 => Key::Unidentified(native), + Button10 => Key::Unidentified(native), + Button11 => Key::Unidentified(native), + Button12 => Key::Unidentified(native), + Button13 => Key::Unidentified(native), + Button14 => Key::Unidentified(native), + Button15 => Key::Unidentified(native), + Button16 => Key::Unidentified(native), + + Yen => Key::Unidentified(native), + Ro => Key::Unidentified(native), + + Assist => Key::Unidentified(native), + + Keycode11 => Key::Unidentified(native), + Keycode12 => Key::Unidentified(native), + + StemPrimary => Key::Unidentified(native), + Stem1 => Key::Unidentified(native), + Stem2 => Key::Unidentified(native), + Stem3 => Key::Unidentified(native), + + DpadUpLeft => Key::Unidentified(native), + DpadDownLeft => Key::Unidentified(native), + DpadUpRight => Key::Unidentified(native), + DpadDownRight => Key::Unidentified(native), + + SoftSleep => Key::Unidentified(native), + + SystemNavigationUp => Key::Unidentified(native), + SystemNavigationDown => Key::Unidentified(native), + SystemNavigationLeft => Key::Unidentified(native), + SystemNavigationRight => Key::Unidentified(native), + + AllApps => Key::Unidentified(native), + ThumbsUp => Key::Unidentified(native), + ThumbsDown => Key::Unidentified(native), + ProfileSwitch => Key::Unidentified(native), + }, } } diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index c40377be..a0a8664a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -24,7 +24,6 @@ use crate::{ error, event::{self, InnerSizeWriter, StartCause}, event_loop::{self, ControlFlow, EventLoopWindowTarget as RootELW}, - keyboard::NativeKey, platform::pump_events::PumpStatus, window::{ self, CursorGrabMode, ImePurpose, ResizeDirection, Theme, WindowButtons, WindowLevel, @@ -151,6 +150,7 @@ pub struct EventLoop { control_flow: ControlFlow, cause: StartCause, ignore_volume_keys: bool, + combining_accent: Option, } #[derive(Debug, Clone, PartialEq)] @@ -214,6 +214,7 @@ impl EventLoop { control_flow: Default::default(), cause: StartCause::Init, ignore_volume_keys: attributes.ignore_volume_keys, + combining_accent: None, } } @@ -353,128 +354,25 @@ impl EventLoop { trace!("No main event to handle"); } + // temporarily decouple `android_app` from `self` so we aren't holding + // a borrow of `self` while iterating + let android_app = self.android_app.clone(); + // Process input events - self.android_app.input_events(|event| { - let mut input_status = InputStatus::Handled; - match event { - InputEvent::MotionEvent(motion_event) => { - let window_id = window::WindowId(WindowId); - let device_id = event::DeviceId(DeviceId); + match android_app.input_events_iter() { + Ok(mut input_iter) => loop { + let read_event = input_iter.next(|event| { + self.handle_input_event(&android_app, event, &mut control_flow, callback) + }); - let phase = match motion_event.action() { - MotionAction::Down | MotionAction::PointerDown => { - Some(event::TouchPhase::Started) - } - MotionAction::Up | MotionAction::PointerUp => { - Some(event::TouchPhase::Ended) - } - MotionAction::Move => Some(event::TouchPhase::Moved), - MotionAction::Cancel => { - Some(event::TouchPhase::Cancelled) - } - _ => { - None // TODO mouse events - } - }; - if let Some(phase) = phase { - let pointers: Box< - dyn Iterator>, - > = match phase { - event::TouchPhase::Started - | event::TouchPhase::Ended => { - Box::new( - std::iter::once(motion_event.pointer_at_index( - motion_event.pointer_index(), - )) - ) - }, - event::TouchPhase::Moved - | event::TouchPhase::Cancelled => { - Box::new(motion_event.pointers()) - } - }; - - for pointer in pointers { - let location = PhysicalPosition { - x: pointer.x() as _, - y: pointer.y() as _, - }; - trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}"); - let event = event::Event::WindowEvent { - window_id, - event: event::WindowEvent::Touch( - event::Touch { - device_id, - phase, - location, - id: pointer.pointer_id() as u64, - force: None, - }, - ), - }; - sticky_exit_callback( - event, - self.window_target(), - &mut control_flow, - callback - ); - } - } - } - InputEvent::KeyEvent(key) => { - match key.key_code() { - // Flag keys related to volume as unhandled. While winit does not have a way for applications - // to configure what keys to flag as handled, this appears to be a good default until winit - // can be configured. - Keycode::VolumeUp | - Keycode::VolumeDown | - Keycode::VolumeMute => { - if self.ignore_volume_keys { - input_status = InputStatus::Unhandled - } - }, - keycode => { - let state = match key.action() { - KeyAction::Down => event::ElementState::Pressed, - KeyAction::Up => event::ElementState::Released, - _ => event::ElementState::Released, - }; - - let native = NativeKey::Android(keycode.into()); - let logical_key = keycodes::to_logical(keycode, native); - // TODO: maybe use getUnicodeChar to get the logical key - - let event = event::Event::WindowEvent { - window_id: window::WindowId(WindowId), - event: event::WindowEvent::KeyboardInput { - device_id: event::DeviceId(DeviceId), - event: event::KeyEvent { - state, - physical_key: keycodes::to_physical_keycode(keycode), - logical_key, - location: keycodes::to_location(keycode), - repeat: key.repeat_count() > 0, - text: None, - platform_specific: KeyEventExtra {}, - }, - is_synthetic: false, - }, - }; - sticky_exit_callback( - event, - self.window_target(), - &mut control_flow, - callback, - ); - } - } - } - _ => { - warn!("Unknown android_activity input event {event:?}") + if !read_event { + break; } + }, + Err(err) => { + log::warn!("Failed to get input events iterator: {err:?}"); } - input_status - }); + } // Empty the user event buffer { @@ -524,6 +422,117 @@ impl EventLoop { self.pending_redraw = pending_redraw; } + fn handle_input_event( + &mut self, + android_app: &AndroidApp, + event: &InputEvent<'_>, + control_flow: &mut ControlFlow, + callback: &mut F, + ) -> InputStatus + where + F: FnMut(event::Event, &RootELW, &mut ControlFlow), + { + let mut input_status = InputStatus::Handled; + match event { + InputEvent::MotionEvent(motion_event) => { + let window_id = window::WindowId(WindowId); + let device_id = event::DeviceId(DeviceId); + + let phase = match motion_event.action() { + MotionAction::Down | MotionAction::PointerDown => { + Some(event::TouchPhase::Started) + } + MotionAction::Up | MotionAction::PointerUp => Some(event::TouchPhase::Ended), + MotionAction::Move => Some(event::TouchPhase::Moved), + MotionAction::Cancel => Some(event::TouchPhase::Cancelled), + _ => { + None // TODO mouse events + } + }; + if let Some(phase) = phase { + let pointers: Box>> = + match phase { + event::TouchPhase::Started | event::TouchPhase::Ended => { + Box::new(std::iter::once( + motion_event.pointer_at_index(motion_event.pointer_index()), + )) + } + event::TouchPhase::Moved | event::TouchPhase::Cancelled => { + Box::new(motion_event.pointers()) + } + }; + + for pointer in pointers { + let location = PhysicalPosition { + x: pointer.x() as _, + y: pointer.y() as _, + }; + trace!("Input event {device_id:?}, {phase:?}, loc={location:?}, pointer={pointer:?}"); + let event = event::Event::WindowEvent { + window_id, + event: event::WindowEvent::Touch(event::Touch { + device_id, + phase, + location, + id: pointer.pointer_id() as u64, + force: None, + }), + }; + sticky_exit_callback(event, self.window_target(), control_flow, callback); + } + } + } + InputEvent::KeyEvent(key) => { + match key.key_code() { + // Flag keys related to volume as unhandled. While winit does not have a way for applications + // to configure what keys to flag as handled, this appears to be a good default until winit + // can be configured. + Keycode::VolumeUp | Keycode::VolumeDown | Keycode::VolumeMute => { + if self.ignore_volume_keys { + input_status = InputStatus::Unhandled + } + } + keycode => { + let state = match key.action() { + KeyAction::Down => event::ElementState::Pressed, + KeyAction::Up => event::ElementState::Released, + _ => event::ElementState::Released, + }; + + let key_char = keycodes::character_map_and_combine_key( + android_app, + key, + &mut self.combining_accent, + ); + + let event = event::Event::WindowEvent { + window_id: window::WindowId(WindowId), + event: event::WindowEvent::KeyboardInput { + device_id: event::DeviceId(DeviceId), + event: event::KeyEvent { + state, + physical_key: keycodes::to_physical_keycode(keycode), + logical_key: keycodes::to_logical(key_char, keycode), + location: keycodes::to_location(keycode), + repeat: key.repeat_count() > 0, + text: None, + platform_specific: KeyEventExtra {}, + }, + is_synthetic: false, + }, + }; + sticky_exit_callback(event, self.window_target(), control_flow, callback); + } + } + } + _ => { + warn!("Unknown android_activity input event {event:?}") + } + } + + input_status + } + pub fn run(mut self, event_handler: F) -> Result<(), RunLoopError> where F: FnMut(event::Event, &event_loop::EventLoopWindowTarget, &mut ControlFlow),