Skip to main content

xcomponent_sys/
keyboard_types_compat.rs

1//! Conversions from XComponent key events to [`keyboard_types`] values,
2//! including US-layout character computation.
3//!
4//! OpenHarmony currently has no keyboard-layout API and the assumption
5//! seems to be US keyboard layout. Until there is an official OH API for
6//! keyboard layout, the best we can do is provide tracking and conversion
7//! for US layout here (Note: This will move to an abstraction crate in the future)
8
9use std::collections::HashSet;
10use std::hash::{BuildHasherDefault, DefaultHasher};
11
12use crate::{OH_NativeXComponent_KeyAction, OH_NativeXComponent_KeyCode};
13use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Location, Modifiers, NamedKey};
14
15impl From<OH_NativeXComponent_KeyCode> for Code {
16    fn from(code: OH_NativeXComponent_KeyCode) -> Self {
17        use OH_NativeXComponent_KeyCode as OH_KeyCode;
18        match code {
19            OH_KeyCode::KEY_GRAVE => Code::Backquote,
20            OH_KeyCode::KEY_BACKSLASH => Code::Backslash,
21            OH_KeyCode::KEY_LEFT_BRACKET => Code::BracketLeft,
22            OH_KeyCode::KEY_RIGHT_BRACKET => Code::BracketRight,
23            OH_KeyCode::KEY_COMMA => Code::Comma,
24            OH_KeyCode::KEY_0 => Code::Digit0,
25            OH_KeyCode::KEY_1 => Code::Digit1,
26            OH_KeyCode::KEY_2 => Code::Digit2,
27            OH_KeyCode::KEY_3 => Code::Digit3,
28            OH_KeyCode::KEY_4 => Code::Digit4,
29            OH_KeyCode::KEY_5 => Code::Digit5,
30            OH_KeyCode::KEY_6 => Code::Digit6,
31            OH_KeyCode::KEY_7 => Code::Digit7,
32            OH_KeyCode::KEY_8 => Code::Digit8,
33            OH_KeyCode::KEY_9 => Code::Digit9,
34
35            OH_KeyCode::KEY_EQUALS => Code::Equal,
36            OH_KeyCode::KEY_RO => Code::IntlRo,
37            OH_KeyCode::KEY_YEN => Code::IntlYen,
38
39            OH_KeyCode::KEY_A => Code::KeyA,
40            OH_KeyCode::KEY_B => Code::KeyB,
41            OH_KeyCode::KEY_C => Code::KeyC,
42            OH_KeyCode::KEY_D => Code::KeyD,
43            OH_KeyCode::KEY_E => Code::KeyE,
44            OH_KeyCode::KEY_F => Code::KeyF,
45            OH_KeyCode::KEY_G => Code::KeyG,
46            OH_KeyCode::KEY_H => Code::KeyH,
47            OH_KeyCode::KEY_I => Code::KeyI,
48            OH_KeyCode::KEY_J => Code::KeyJ,
49            OH_KeyCode::KEY_K => Code::KeyK,
50            OH_KeyCode::KEY_L => Code::KeyL,
51            OH_KeyCode::KEY_M => Code::KeyM,
52            OH_KeyCode::KEY_N => Code::KeyN,
53            OH_KeyCode::KEY_O => Code::KeyO,
54            OH_KeyCode::KEY_P => Code::KeyP,
55            OH_KeyCode::KEY_Q => Code::KeyQ,
56            OH_KeyCode::KEY_R => Code::KeyR,
57            OH_KeyCode::KEY_S => Code::KeyS,
58            OH_KeyCode::KEY_T => Code::KeyT,
59            OH_KeyCode::KEY_U => Code::KeyU,
60            OH_KeyCode::KEY_V => Code::KeyV,
61            OH_KeyCode::KEY_W => Code::KeyW,
62            OH_KeyCode::KEY_X => Code::KeyX,
63            OH_KeyCode::KEY_Y => Code::KeyY,
64            OH_KeyCode::KEY_Z => Code::KeyZ,
65
66            OH_KeyCode::KEY_MINUS => Code::Minus,
67            OH_KeyCode::KEY_PERIOD => Code::Period,
68            OH_KeyCode::KEY_APOSTROPHE => Code::Quote,
69            OH_KeyCode::KEY_SEMICOLON => Code::Semicolon,
70            OH_KeyCode::KEY_SLASH => Code::Slash,
71            OH_KeyCode::KEY_ALT_LEFT => Code::AltLeft,
72            OH_KeyCode::KEY_ALT_RIGHT => Code::AltRight,
73            OH_KeyCode::KEY_DEL => Code::Backspace,
74            OH_KeyCode::KEY_CAPS_LOCK => Code::CapsLock,
75            OH_KeyCode::KEY_MENU => Code::ContextMenu,
76            OH_KeyCode::KEY_CTRL_LEFT => Code::ControlLeft,
77            OH_KeyCode::KEY_CTRL_RIGHT => Code::ControlRight,
78            OH_KeyCode::KEY_ENTER => Code::Enter,
79            OH_KeyCode::KEY_META_LEFT => Code::MetaLeft,
80            OH_KeyCode::KEY_META_RIGHT => Code::MetaRight,
81            OH_KeyCode::KEY_SHIFT_LEFT => Code::ShiftLeft,
82            OH_KeyCode::KEY_SHIFT_RIGHT => Code::ShiftRight,
83            OH_KeyCode::KEY_SPACE => Code::Space,
84            OH_KeyCode::KEY_TAB => Code::Tab,
85            // Code::Convert
86            // Lang1-5
87            // NonConvert
88            OH_KeyCode::KEY_FORWARD_DEL => Code::Delete,
89            OH_KeyCode::KEY_MOVE_END => Code::End,
90            OH_KeyCode::KEY_HELP => Code::Help,
91            OH_KeyCode::KEY_HOME => Code::Home,
92            OH_KeyCode::KEY_MOVE_HOME => Code::Home,
93            OH_KeyCode::KEY_INSERT => Code::Insert,
94            OH_KeyCode::KEY_PAGE_DOWN => Code::PageDown,
95            OH_KeyCode::KEY_PAGE_UP => Code::PageUp,
96            OH_KeyCode::KEY_DPAD_DOWN => Code::ArrowDown,
97            OH_KeyCode::KEY_DPAD_UP => Code::ArrowUp,
98            OH_KeyCode::KEY_DPAD_LEFT => Code::ArrowLeft,
99            OH_KeyCode::KEY_DPAD_RIGHT => Code::ArrowRight,
100
101            OH_KeyCode::KEY_NUM_LOCK => Code::NumLock,
102            OH_KeyCode::KEY_NUMPAD_0 => Code::Numpad0,
103            OH_KeyCode::KEY_NUMPAD_1 => Code::Numpad1,
104            OH_KeyCode::KEY_NUMPAD_2 => Code::Numpad2,
105            OH_KeyCode::KEY_NUMPAD_3 => Code::Numpad3,
106            OH_KeyCode::KEY_NUMPAD_4 => Code::Numpad4,
107            OH_KeyCode::KEY_NUMPAD_5 => Code::Numpad5,
108            OH_KeyCode::KEY_NUMPAD_6 => Code::Numpad6,
109            OH_KeyCode::KEY_NUMPAD_7 => Code::Numpad7,
110            OH_KeyCode::KEY_NUMPAD_8 => Code::Numpad8,
111            OH_KeyCode::KEY_NUMPAD_9 => Code::Numpad9,
112            OH_KeyCode::KEY_NUMPAD_ADD => Code::NumpadAdd,
113            OH_KeyCode::KEY_NUMPAD_COMMA => Code::NumpadComma,
114            OH_KeyCode::KEY_NUMPAD_DIVIDE => Code::NumpadDivide,
115            OH_KeyCode::KEY_NUMPAD_DOT => Code::NumpadDecimal,
116            OH_KeyCode::KEY_NUMPAD_ENTER => Code::NumpadEnter,
117            OH_KeyCode::KEY_NUMPAD_EQUALS => Code::NumpadEqual,
118            OH_KeyCode::KEY_NUMPAD_LEFT_PAREN => Code::NumpadParenLeft,
119            OH_KeyCode::KEY_NUMPAD_RIGHT_PAREN => Code::NumpadParenRight,
120            OH_KeyCode::KEY_NUMPAD_MULTIPLY => Code::NumpadMultiply,
121            // OH_KeyCode::KEY_NUMPAD_PLUSMINUS => Code::Numpad
122            OH_KeyCode::KEY_NUMPAD_SUBTRACT => Code::NumpadSubtract,
123
124            OH_KeyCode::KEY_ESCAPE => Code::Escape,
125            OH_KeyCode::KEY_FN => Code::Fn,
126            // apparently no fn lock
127            OH_KeyCode::KEY_SYSRQ => Code::PrintScreen,
128            OH_KeyCode::KEY_PRINT => Code::PrintScreen,
129            OH_KeyCode::KEY_SCROLL_LOCK => Code::ScrollLock,
130            OH_KeyCode::KEY_BREAK => Code::Pause,
131            OH_KeyCode::KEY_FORWARD => Code::BrowserForward,
132            // Code Browser*
133            OH_KeyCode::KEY_MEDIA_EJECT => Code::Eject,
134            OH_KeyCode::KEY_ENVELOPE => Code::LaunchMail,
135            // Code Launch*
136            OH_KeyCode::KEY_MEDIA_PLAY_PAUSE => Code::MediaPlayPause,
137            OH_KeyCode::KEY_PLAYPAUSE => Code::MediaPlayPause,
138            OH_KeyCode::KEY_MEDIA_PAUSE => Code::MediaPause,
139            OH_KeyCode::KEY_MEDIA_PLAY => Code::MediaPlay,
140            OH_KeyCode::KEY_MEDIA_NEXT => Code::MediaTrackNext,
141            OH_KeyCode::KEY_MEDIA_PREVIOUS => Code::MediaTrackPrevious,
142            OH_KeyCode::KEY_MEDIA_STOP => Code::MediaStop,
143            OH_KeyCode::KEY_MEDIA_RECORD => Code::MediaRecord,
144            OH_KeyCode::KEY_MEDIA_REWIND => Code::MediaRewind,
145            OH_KeyCode::KEY_MEDIA_FAST_FORWARD => Code::MediaFastForward,
146
147            OH_KeyCode::KEY_POWER => Code::Power,
148            OH_KeyCode::KEY_SLEEP => Code::Sleep,
149
150            OH_KeyCode::KEY_VOLUME_DOWN => Code::AudioVolumeDown,
151            OH_KeyCode::KEY_VOLUME_UP => Code::AudioVolumeUp,
152            OH_KeyCode::KEY_VOLUME_MUTE => Code::AudioVolumeMute,
153
154            OH_KeyCode::KEY_WAKEUP => Code::WakeUp,
155            // Hyper, Super, Turbo, Abort, Resume
156            OH_KeyCode::KEY_SUSPEND => Code::Suspend,
157            OH_KeyCode::KEY_AGAIN => Code::Again,
158            OH_KeyCode::KEY_COPY => Code::Copy,
159            OH_KeyCode::KEY_CUT => Code::Cut,
160            OH_KeyCode::KEY_FIND => Code::Find,
161            OH_KeyCode::KEY_OPEN => Code::Open,
162            OH_KeyCode::KEY_PASTE => Code::Paste,
163            OH_KeyCode::KEY_PROPS => Code::Props,
164            // Select
165            OH_KeyCode::KEY_UNDO => Code::Undo,
166            OH_KeyCode::KEY_HIRAGANA => Code::Hiragana,
167            OH_KeyCode::KEY_KATAKANA => Code::Katakana,
168            OH_KeyCode::KEY_F1 => Code::F1,
169            OH_KeyCode::KEY_F2 => Code::F2,
170            OH_KeyCode::KEY_F3 => Code::F3,
171            OH_KeyCode::KEY_F4 => Code::F4,
172            OH_KeyCode::KEY_F5 => Code::F5,
173            OH_KeyCode::KEY_F6 => Code::F6,
174            OH_KeyCode::KEY_F7 => Code::F7,
175            OH_KeyCode::KEY_F8 => Code::F8,
176            OH_KeyCode::KEY_F9 => Code::F9,
177            OH_KeyCode::KEY_F10 => Code::F10,
178            OH_KeyCode::KEY_F11 => Code::F11,
179            OH_KeyCode::KEY_F12 => Code::F12,
180            OH_KeyCode::KEY_F13 => Code::F13,
181            OH_KeyCode::KEY_F14 => Code::F14,
182            OH_KeyCode::KEY_F15 => Code::F15,
183            OH_KeyCode::KEY_F16 => Code::F16,
184            OH_KeyCode::KEY_F17 => Code::F17,
185            OH_KeyCode::KEY_F18 => Code::F18,
186            OH_KeyCode::KEY_F19 => Code::F19,
187            OH_KeyCode::KEY_F20 => Code::F20,
188            OH_KeyCode::KEY_F21 => Code::F21,
189            OH_KeyCode::KEY_F22 => Code::F22,
190            OH_KeyCode::KEY_F23 => Code::F23,
191            OH_KeyCode::KEY_F24 => Code::F24,
192            OH_KeyCode::KEY_BRIGHTNESS_DOWN => Code::BrightnessDown,
193            OH_KeyCode::KEY_BRIGHTNESS_UP => Code::BrightnessUp,
194            // DisplayToggleIntExt?
195            // OH_KeyCode::KEY_KBD_LAYOUT_NEXT => Code::KeyboardLayoutSelect
196            OH_KeyCode::KEY_MUTE => Code::MicrophoneMuteToggle,
197            OH_KeyCode::KEY_APPSELECT => Code::SelectTask,
198            // todo: verify
199            OH_KeyCode::KEY_CYCLEWINDOWS => Code::ShowAllWindows,
200            // OH_KeyCode::KEY_ZOOMIN => Code::
201            OH_KeyCode::KEY_UNKNOWN => Code::Unidentified,
202            _ => Code::Unidentified,
203        }
204    }
205}
206
207pub struct UnknownKeyState {}
208
209impl TryFrom<OH_NativeXComponent_KeyAction> for KeyState {
210    type Error = UnknownKeyState;
211
212    fn try_from(value: OH_NativeXComponent_KeyAction) -> Result<Self, Self::Error> {
213        use OH_NativeXComponent_KeyAction as KeyAction;
214        match value {
215            KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_UP => Ok(KeyState::Up),
216            KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_DOWN => Ok(KeyState::Down),
217            _ => Err(UnknownKeyState {}),
218        }
219    }
220}
221
222/// Logical key value for key codes that never produce a character.
223///
224/// Character keys (including numpad keys, whose value depends on NumLock) are
225/// resolved through [`us_char`] first; this map only covers the remaining
226/// non-printable keys.
227pub fn named_key(code: OH_NativeXComponent_KeyCode) -> Option<NamedKey> {
228    use OH_NativeXComponent_KeyCode as OH_KeyCode;
229    let key = match code {
230        OH_KeyCode::KEY_FN | OH_KeyCode::KEY_FUNCTION => NamedKey::Fn,
231        OH_KeyCode::KEY_HOME => NamedKey::GoHome,
232        OH_KeyCode::KEY_BACK => NamedKey::GoBack,
233        OH_KeyCode::KEY_MEDIA_PLAY_PAUSE | OH_KeyCode::KEY_PLAYPAUSE => NamedKey::MediaPlayPause,
234        OH_KeyCode::KEY_MEDIA_STOP => NamedKey::MediaStop,
235        OH_KeyCode::KEY_MEDIA_NEXT => NamedKey::MediaTrackNext,
236        OH_KeyCode::KEY_MEDIA_PREVIOUS => NamedKey::MediaTrackPrevious,
237        OH_KeyCode::KEY_MEDIA_REWIND => NamedKey::MediaRewind,
238        OH_KeyCode::KEY_MEDIA_FAST_FORWARD => NamedKey::MediaFastForward,
239        OH_KeyCode::KEY_VOLUME_UP => NamedKey::AudioVolumeUp,
240        OH_KeyCode::KEY_VOLUME_DOWN => NamedKey::AudioVolumeDown,
241        OH_KeyCode::KEY_POWER => NamedKey::Power,
242        OH_KeyCode::KEY_CAMERA => NamedKey::Camera,
243        OH_KeyCode::KEY_VOLUME_MUTE => NamedKey::AudioVolumeMute,
244        OH_KeyCode::KEY_MUTE => NamedKey::MicrophoneVolumeMute,
245        OH_KeyCode::KEY_BRIGHTNESS_UP => NamedKey::BrightnessUp,
246        OH_KeyCode::KEY_BRIGHTNESS_DOWN => NamedKey::BrightnessDown,
247        OH_KeyCode::KEY_DPAD_UP => NamedKey::ArrowUp,
248        OH_KeyCode::KEY_DPAD_DOWN => NamedKey::ArrowDown,
249        OH_KeyCode::KEY_DPAD_LEFT => NamedKey::ArrowLeft,
250        OH_KeyCode::KEY_DPAD_RIGHT => NamedKey::ArrowRight,
251        OH_KeyCode::KEY_DPAD_CENTER => NamedKey::Select,
252        OH_KeyCode::KEY_ALT_LEFT | OH_KeyCode::KEY_ALT_RIGHT => NamedKey::Alt,
253        OH_KeyCode::KEY_SHIFT_LEFT | OH_KeyCode::KEY_SHIFT_RIGHT => NamedKey::Shift,
254        OH_KeyCode::KEY_TAB => NamedKey::Tab,
255        OH_KeyCode::KEY_SYM => NamedKey::Symbol,
256        OH_KeyCode::KEY_EXPLORER => NamedKey::LaunchWebBrowser,
257        OH_KeyCode::KEY_ENVELOPE => NamedKey::LaunchMail,
258        OH_KeyCode::KEY_ENTER | OH_KeyCode::KEY_NUMPAD_ENTER => NamedKey::Enter,
259        OH_KeyCode::KEY_DEL => NamedKey::Backspace,
260        OH_KeyCode::KEY_MENU => NamedKey::ContextMenu,
261        OH_KeyCode::KEY_PAGE_UP => NamedKey::PageUp,
262        OH_KeyCode::KEY_PAGE_DOWN => NamedKey::PageDown,
263        OH_KeyCode::KEY_ESCAPE => NamedKey::Escape,
264        OH_KeyCode::KEY_FORWARD_DEL => NamedKey::Delete,
265        OH_KeyCode::KEY_CTRL_LEFT | OH_KeyCode::KEY_CTRL_RIGHT => NamedKey::Control,
266        OH_KeyCode::KEY_CAPS_LOCK => NamedKey::CapsLock,
267        OH_KeyCode::KEY_SCROLL_LOCK => NamedKey::ScrollLock,
268        OH_KeyCode::KEY_META_LEFT | OH_KeyCode::KEY_META_RIGHT => NamedKey::Meta,
269        OH_KeyCode::KEY_SYSRQ | OH_KeyCode::KEY_PRINT => NamedKey::PrintScreen,
270        OH_KeyCode::KEY_BREAK => NamedKey::Pause,
271        OH_KeyCode::KEY_MOVE_HOME => NamedKey::Home,
272        OH_KeyCode::KEY_MOVE_END => NamedKey::End,
273        OH_KeyCode::KEY_INSERT => NamedKey::Insert,
274        OH_KeyCode::KEY_FORWARD => NamedKey::BrowserForward,
275        OH_KeyCode::KEY_MEDIA_PLAY => NamedKey::MediaPlay,
276        OH_KeyCode::KEY_MEDIA_PAUSE => NamedKey::MediaPause,
277        OH_KeyCode::KEY_MEDIA_CLOSE => NamedKey::MediaClose,
278        OH_KeyCode::KEY_MEDIA_EJECT => NamedKey::Eject,
279        OH_KeyCode::KEY_MEDIA_RECORD => NamedKey::MediaRecord,
280        OH_KeyCode::KEY_F1 => NamedKey::F1,
281        OH_KeyCode::KEY_F2 => NamedKey::F2,
282        OH_KeyCode::KEY_F3 => NamedKey::F3,
283        OH_KeyCode::KEY_F4 => NamedKey::F4,
284        OH_KeyCode::KEY_F5 => NamedKey::F5,
285        OH_KeyCode::KEY_F6 => NamedKey::F6,
286        OH_KeyCode::KEY_F7 => NamedKey::F7,
287        OH_KeyCode::KEY_F8 => NamedKey::F8,
288        OH_KeyCode::KEY_F9 => NamedKey::F9,
289        OH_KeyCode::KEY_F10 => NamedKey::F10,
290        OH_KeyCode::KEY_F11 => NamedKey::F11,
291        OH_KeyCode::KEY_F12 => NamedKey::F12,
292        OH_KeyCode::KEY_F13 => NamedKey::F13,
293        OH_KeyCode::KEY_F14 => NamedKey::F14,
294        OH_KeyCode::KEY_F15 => NamedKey::F15,
295        OH_KeyCode::KEY_F16 => NamedKey::F16,
296        OH_KeyCode::KEY_F17 => NamedKey::F17,
297        OH_KeyCode::KEY_F18 => NamedKey::F18,
298        OH_KeyCode::KEY_F19 => NamedKey::F19,
299        OH_KeyCode::KEY_F20 => NamedKey::F20,
300        OH_KeyCode::KEY_F21 => NamedKey::F21,
301        OH_KeyCode::KEY_F22 => NamedKey::F22,
302        OH_KeyCode::KEY_F23 => NamedKey::F23,
303        OH_KeyCode::KEY_F24 => NamedKey::F24,
304        OH_KeyCode::KEY_NUM_LOCK => NamedKey::NumLock,
305        OH_KeyCode::KEY_SLEEP => NamedKey::Standby,
306        OH_KeyCode::KEY_WAKEUP => NamedKey::WakeUp,
307        OH_KeyCode::KEY_HELP => NamedKey::Help,
308        OH_KeyCode::KEY_AGAIN => NamedKey::Again,
309        OH_KeyCode::KEY_PROPS => NamedKey::Props,
310        OH_KeyCode::KEY_UNDO => NamedKey::Undo,
311        OH_KeyCode::KEY_COPY => NamedKey::Copy,
312        OH_KeyCode::KEY_OPEN => NamedKey::Open,
313        OH_KeyCode::KEY_PASTE => NamedKey::Paste,
314        OH_KeyCode::KEY_FIND => NamedKey::Find,
315        OH_KeyCode::KEY_CUT => NamedKey::Cut,
316        OH_KeyCode::KEY_HIRAGANA => NamedKey::Hiragana,
317        OH_KeyCode::KEY_KATAKANA => NamedKey::Katakana,
318        OH_KeyCode::KEY_APPSELECT => NamedKey::AppSwitch,
319        _ => return None,
320    };
321    Some(key)
322}
323
324/// Navigation key a numpad digit or dot key acts as while NumLock is off.
325fn numpad_nav_key(code: Code) -> Option<NamedKey> {
326    let key = match code {
327        Code::Numpad0 => NamedKey::Insert,
328        Code::Numpad1 => NamedKey::End,
329        Code::Numpad2 => NamedKey::ArrowDown,
330        Code::Numpad3 => NamedKey::PageDown,
331        Code::Numpad4 => NamedKey::ArrowLeft,
332        Code::Numpad5 => NamedKey::Clear,
333        Code::Numpad6 => NamedKey::ArrowRight,
334        Code::Numpad7 => NamedKey::Home,
335        Code::Numpad8 => NamedKey::ArrowUp,
336        Code::Numpad9 => NamedKey::PageUp,
337        Code::NumpadDecimal => NamedKey::Delete,
338        _ => return None,
339    };
340    Some(key)
341}
342
343/// `location` value for a key code. `NumLock` itself is `Standard`, per the
344/// UI Events specification.
345pub fn location(code: OH_NativeXComponent_KeyCode) -> Location {
346    use OH_NativeXComponent_KeyCode as OH_KeyCode;
347    match code {
348        OH_KeyCode::KEY_ALT_LEFT
349        | OH_KeyCode::KEY_SHIFT_LEFT
350        | OH_KeyCode::KEY_CTRL_LEFT
351        | OH_KeyCode::KEY_META_LEFT => Location::Left,
352        OH_KeyCode::KEY_ALT_RIGHT
353        | OH_KeyCode::KEY_SHIFT_RIGHT
354        | OH_KeyCode::KEY_CTRL_RIGHT
355        | OH_KeyCode::KEY_META_RIGHT => Location::Right,
356        OH_KeyCode::KEY_NUMPAD_PLUSMINUS => Location::Numpad,
357        code if (OH_KeyCode::KEY_NUMPAD_0.0..=OH_KeyCode::KEY_NUMPAD_RIGHT_PAREN.0)
358            .contains(&code.0) =>
359        {
360            Location::Numpad
361        }
362        _ => Location::Standard,
363    }
364}
365
366/// US-layout `(unshifted, shifted)` character pair for a physical key.
367///
368/// Derived from the `KEY_UNICODE_TRANSFORMATION` table of the OpenHarmony
369/// multimodalinput service (multimodalinput_input @ 02182f08, Apache-2.0 like
370/// this repo), re-keyed by [`Code`]. Deviations from upstream: Space is added
371/// (absent upstream, so the platform mapper types nothing for it), and numpad
372/// keys carry their character here while [`us_char`] gates the digit and dot
373/// keys on NumLock (upstream ignores NumLock and gives numpad keys no shifted
374/// character at all).
375fn us_char_pair(code: Code) -> Option<(char, char)> {
376    let pair = match code {
377        Code::Digit0 => ('0', ')'),
378        Code::Digit1 => ('1', '!'),
379        Code::Digit2 => ('2', '@'),
380        Code::Digit3 => ('3', '#'),
381        Code::Digit4 => ('4', '$'),
382        Code::Digit5 => ('5', '%'),
383        Code::Digit6 => ('6', '^'),
384        Code::Digit7 => ('7', '&'),
385        Code::Digit8 => ('8', '*'),
386        Code::Digit9 => ('9', '('),
387        Code::KeyA => ('a', 'A'),
388        Code::KeyB => ('b', 'B'),
389        Code::KeyC => ('c', 'C'),
390        Code::KeyD => ('d', 'D'),
391        Code::KeyE => ('e', 'E'),
392        Code::KeyF => ('f', 'F'),
393        Code::KeyG => ('g', 'G'),
394        Code::KeyH => ('h', 'H'),
395        Code::KeyI => ('i', 'I'),
396        Code::KeyJ => ('j', 'J'),
397        Code::KeyK => ('k', 'K'),
398        Code::KeyL => ('l', 'L'),
399        Code::KeyM => ('m', 'M'),
400        Code::KeyN => ('n', 'N'),
401        Code::KeyO => ('o', 'O'),
402        Code::KeyP => ('p', 'P'),
403        Code::KeyQ => ('q', 'Q'),
404        Code::KeyR => ('r', 'R'),
405        Code::KeyS => ('s', 'S'),
406        Code::KeyT => ('t', 'T'),
407        Code::KeyU => ('u', 'U'),
408        Code::KeyV => ('v', 'V'),
409        Code::KeyW => ('w', 'W'),
410        Code::KeyX => ('x', 'X'),
411        Code::KeyY => ('y', 'Y'),
412        Code::KeyZ => ('z', 'Z'),
413        Code::Comma => (',', '<'),
414        Code::Period => ('.', '>'),
415        Code::Backquote => ('`', '~'),
416        Code::Minus => ('-', '_'),
417        Code::Equal => ('=', '+'),
418        Code::BracketLeft => ('[', '{'),
419        Code::BracketRight => (']', '}'),
420        Code::Backslash => ('\\', '|'),
421        Code::Semicolon => (';', ':'),
422        Code::Quote => ('\'', '"'),
423        Code::Slash => ('/', '?'),
424        Code::Space => (' ', ' '),
425        Code::Numpad0 => ('0', '0'),
426        Code::Numpad1 => ('1', '1'),
427        Code::Numpad2 => ('2', '2'),
428        Code::Numpad3 => ('3', '3'),
429        Code::Numpad4 => ('4', '4'),
430        Code::Numpad5 => ('5', '5'),
431        Code::Numpad6 => ('6', '6'),
432        Code::Numpad7 => ('7', '7'),
433        Code::Numpad8 => ('8', '8'),
434        Code::Numpad9 => ('9', '9'),
435        Code::NumpadDivide => ('/', '/'),
436        Code::NumpadMultiply => ('*', '*'),
437        Code::NumpadSubtract => ('-', '-'),
438        Code::NumpadAdd => ('+', '+'),
439        Code::NumpadDecimal => ('.', '.'),
440        _ => return None,
441    };
442    Some(pair)
443}
444
445fn is_letter(code: Code) -> bool {
446    matches!(
447        code,
448        Code::KeyA
449            | Code::KeyB
450            | Code::KeyC
451            | Code::KeyD
452            | Code::KeyE
453            | Code::KeyF
454            | Code::KeyG
455            | Code::KeyH
456            | Code::KeyI
457            | Code::KeyJ
458            | Code::KeyK
459            | Code::KeyL
460            | Code::KeyM
461            | Code::KeyN
462            | Code::KeyO
463            | Code::KeyP
464            | Code::KeyQ
465            | Code::KeyR
466            | Code::KeyS
467            | Code::KeyT
468            | Code::KeyU
469            | Code::KeyV
470            | Code::KeyW
471            | Code::KeyX
472            | Code::KeyY
473            | Code::KeyZ
474    )
475}
476
477fn is_numpad_digit_or_decimal(code: Code) -> bool {
478    matches!(
479        code,
480        Code::Numpad0
481            | Code::Numpad1
482            | Code::Numpad2
483            | Code::Numpad3
484            | Code::Numpad4
485            | Code::Numpad5
486            | Code::Numpad6
487            | Code::Numpad7
488            | Code::Numpad8
489            | Code::Numpad9
490            | Code::NumpadDecimal
491    )
492}
493
494fn is_numpad_operator(code: Code) -> bool {
495    matches!(
496        code,
497        Code::NumpadDivide | Code::NumpadMultiply | Code::NumpadSubtract | Code::NumpadAdd
498    )
499}
500
501/// Character produced by a physical key on the US layout, following the
502/// platform's own `KeyCodeToUnicode` logic: letters use the shifted character
503/// when Shift XOR CapsLock, other keys when Shift. `None` when the key
504/// produces no character.
505///
506/// Deliberate deviation from the platform (which ignores NumLock): numpad
507/// digit and dot keys produce their character only while `num_lock` is true
508/// and `None` otherwise ([`KeyEventConverter`] then maps them to the
509/// corresponding navigation keys); the numpad operator keys (divide, multiply,
510/// subtract, add) produce their character regardless of NumLock, and Shift
511/// does not affect the numpad.
512pub fn us_char(code: Code, shift: bool, caps_lock: bool, num_lock: bool) -> Option<char> {
513    let (unshifted, shifted) = us_char_pair(code)?;
514    if is_numpad_digit_or_decimal(code) {
515        return num_lock.then_some(unshifted);
516    }
517    if is_numpad_operator(code) {
518        return Some(unshifted);
519    }
520    let use_shifted = if is_letter(code) {
521        shift != caps_lock
522    } else {
523        shift
524    };
525    Some(if use_shifted { shifted } else { unshifted })
526}
527
528/// Modifier and lock state as reported by the platform alongside a key event.
529#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
530pub struct ModifierState {
531    pub shift: bool,
532    pub ctrl: bool,
533    pub alt: bool,
534    /// `None` when the platform cannot report Meta; [`KeyEventConverter`]
535    /// then substitutes the state it tracks from `KEY_META_LEFT` /
536    /// `KEY_META_RIGHT` transitions.
537    pub meta: Option<bool>,
538    pub caps_lock: bool,
539    pub num_lock: bool,
540    pub scroll_lock: bool,
541}
542
543/// Stateful translator from XComponent key events to
544/// [`keyboard_types::KeyboardEvent`].
545///
546/// The state tracks which key codes are down (to derive `repeat`) and the Meta
547/// keys (used when [`ModifierState::meta`] is `None`). Feed it every key event
548/// of one physical keyboard, in order, and reset it with
549/// [`clear`](Self::clear) on focus loss. Returns `None` for an unknown key
550/// action.
551///
552/// Note: this stateful helper logically belongs in a safe XComponent wrapper
553/// crate and will move there when such an abstraction crate is created; it
554/// lives here until then.
555///
556/// ```
557/// use keyboard_types::{Code, Key};
558/// use xcomponent_sys::keyboard_types_compat::{KeyEventConverter, ModifierState};
559/// use xcomponent_sys::{OH_NativeXComponent_KeyAction, OH_NativeXComponent_KeyCode};
560///
561/// let mut converter = KeyEventConverter::new();
562/// let shifted = ModifierState {
563///     shift: true,
564///     ..Default::default()
565/// };
566/// let event = converter
567///     .convert(
568///         OH_NativeXComponent_KeyAction::OH_NATIVEXCOMPONENT_KEY_ACTION_DOWN,
569///         OH_NativeXComponent_KeyCode::KEY_A,
570///         shifted,
571///     )
572///     .unwrap();
573/// assert_eq!(event.key, Key::Character("A".to_string()));
574/// assert_eq!(event.code, Code::KeyA);
575/// ```
576#[derive(Clone, Debug, Default)]
577pub struct KeyEventConverter {
578    pressed: HashSet<OH_NativeXComponent_KeyCode, BuildHasherDefault<DefaultHasher>>,
579    meta_left: bool,
580    meta_right: bool,
581}
582
583impl KeyEventConverter {
584    pub const fn new() -> Self {
585        Self {
586            pressed: HashSet::with_hasher(BuildHasherDefault::new()),
587            meta_left: false,
588            meta_right: false,
589        }
590    }
591
592    /// Reset all tracked key state.
593    pub fn clear(&mut self) {
594        self.pressed.clear();
595        self.meta_left = false;
596        self.meta_right = false;
597    }
598
599    pub fn convert(
600        &mut self,
601        action: OH_NativeXComponent_KeyAction,
602        key_code: OH_NativeXComponent_KeyCode,
603        modifiers: ModifierState,
604    ) -> Option<KeyboardEvent> {
605        use OH_NativeXComponent_KeyCode as OH_KeyCode;
606        let state = KeyState::try_from(action).ok()?;
607        let down = state == KeyState::Down;
608        if key_code == OH_KeyCode::KEY_META_LEFT {
609            self.meta_left = down;
610        } else if key_code == OH_KeyCode::KEY_META_RIGHT {
611            self.meta_right = down;
612        }
613        let repeat = if down {
614            !self.pressed.insert(key_code)
615        } else {
616            self.pressed.remove(&key_code);
617            false
618        };
619
620        let meta = modifiers.meta.unwrap_or(self.meta_left || self.meta_right);
621        let mut flags = Modifiers::empty();
622        flags.set(Modifiers::SHIFT, modifiers.shift);
623        flags.set(Modifiers::CONTROL, modifiers.ctrl);
624        flags.set(Modifiers::ALT, modifiers.alt);
625        flags.set(Modifiers::META, meta);
626        flags.set(Modifiers::CAPS_LOCK, modifiers.caps_lock);
627        flags.set(Modifiers::NUM_LOCK, modifiers.num_lock);
628        flags.set(Modifiers::SCROLL_LOCK, modifiers.scroll_lock);
629
630        let code = Code::from(key_code);
631        let key = match us_char(
632            code,
633            modifiers.shift,
634            modifiers.caps_lock,
635            modifiers.num_lock,
636        ) {
637            Some(character) => Key::Character(character.to_string()),
638            None => Key::Named(
639                numpad_nav_key(code)
640                    .or_else(|| named_key(key_code))
641                    .unwrap_or(NamedKey::Unidentified),
642            ),
643        };
644
645        Some(KeyboardEvent {
646            state,
647            key,
648            code,
649            location: location(key_code),
650            modifiers: flags,
651            repeat,
652            is_composing: false,
653        })
654    }
655}