pty/
key_util.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// A struct which can be used to convert a code point to a string.
6pub struct CodePoint {
7    pub code_point: u32,
8    pub control_pressed: bool,
9}
10
11impl From<CodePoint> for Option<String> {
12    fn from(item: CodePoint) -> Self {
13        // We currently don't support higher code points.
14        if item.code_point > 128 {
15            return None;
16        }
17
18        let mut code_point = item.code_point;
19        // Convert to a control code if we are holding ctrl.
20        if item.control_pressed {
21            if let Some(c) = std::char::from_u32(item.code_point) {
22                match c {
23                    'a'..='z' => code_point = code_point - 96,
24                    'A'..='Z' => code_point = code_point - 64,
25                    _ => (),
26                };
27            }
28        }
29
30        String::from_utf8(vec![code_point as u8]).ok()
31    }
32}
33
34const HID_USAGE_KEY_ENTER: u32 = 0x28;
35const HID_USAGE_KEY_ESC: u32 = 0x29;
36const HID_USAGE_KEY_BACKSPACE: u32 = 0x2a;
37const HID_USAGE_KEY_TAB: u32 = 0x2b;
38const HID_USAGE_KEY_INSERT: u32 = 0x49;
39const HID_USAGE_KEY_HOME: u32 = 0x4a;
40const HID_USAGE_KEY_PAGEUP: u32 = 0x4b;
41const HID_USAGE_KEY_DELETE: u32 = 0x4c;
42const HID_USAGE_KEY_END: u32 = 0x4d;
43const HID_USAGE_KEY_PAGEDOWN: u32 = 0x4e;
44const HID_USAGE_KEY_RIGHT: u32 = 0x4f;
45const HID_USAGE_KEY_LEFT: u32 = 0x50;
46const HID_USAGE_KEY_DOWN: u32 = 0x51;
47const HID_USAGE_KEY_UP: u32 = 0x52;
48
49/// A struct which can be used to convert a hid_usage to a suitable string.
50pub struct HidUsage {
51    pub hid_usage: u32,
52    pub app_cursor: bool,
53}
54
55impl From<HidUsage> for Option<String> {
56    fn from(item: HidUsage) -> Self {
57        let esc: char = 0x1b_u8.into();
58        macro_rules! escape_string {
59            ($x:expr) => {{
60                Some(format!("{}{}", esc, $x))
61            }};
62        }
63
64        let app_cursor = item.app_cursor;
65        match item.hid_usage {
66            HID_USAGE_KEY_BACKSPACE => Some(String::from("\x7f")),
67            HID_USAGE_KEY_ESC => escape_string!(""),
68            HID_USAGE_KEY_PAGEDOWN => escape_string!("[6~"),
69            HID_USAGE_KEY_PAGEUP => escape_string!("[5~"),
70            HID_USAGE_KEY_END if app_cursor => escape_string!("OF"),
71            HID_USAGE_KEY_END => escape_string!("[F"),
72            HID_USAGE_KEY_HOME if app_cursor => escape_string!("OH"),
73            HID_USAGE_KEY_HOME => escape_string!("[H"),
74            HID_USAGE_KEY_LEFT if app_cursor => escape_string!("OD"),
75            HID_USAGE_KEY_LEFT => escape_string!("[D"),
76            HID_USAGE_KEY_UP if app_cursor => escape_string!("OA"),
77            HID_USAGE_KEY_UP => escape_string!("[A"),
78            HID_USAGE_KEY_RIGHT if app_cursor => escape_string!("OC"),
79            HID_USAGE_KEY_RIGHT => escape_string!("[C"),
80            HID_USAGE_KEY_DOWN if app_cursor => escape_string!("OB"),
81            HID_USAGE_KEY_DOWN => escape_string!("[B"),
82            HID_USAGE_KEY_INSERT => escape_string!("[2~"),
83            HID_USAGE_KEY_DELETE => escape_string!("[3~"),
84            HID_USAGE_KEY_ENTER => Some(String::from("\n")),
85            HID_USAGE_KEY_TAB => Some(String::from("\t")),
86            _ => None,
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn convert_from_code_point_unsupported_values() {
97        assert!(
98            Option::<String>::from(CodePoint { code_point: 129, control_pressed: false }).is_none()
99        );
100    }
101
102    #[test]
103    fn convert_from_code_point_shifts_when_control_pressed_lowercase() {
104        let mut i = 0;
105        for c in (b'a'..=b'z').map(char::from) {
106            i = i + 1;
107            let result =
108                Option::<String>::from(CodePoint { code_point: c as u32, control_pressed: true })
109                    .unwrap();
110            let expected = String::from_utf8(vec![i]).unwrap();
111            assert_eq!(result, expected);
112        }
113    }
114
115    #[test]
116    fn convert_from_code_point_shifts_when_control_pressed_uppercase() {
117        let mut i = 0;
118        for c in (b'A'..=b'Z').map(char::from) {
119            i = i + 1;
120            let result =
121                Option::<String>::from(CodePoint { code_point: c as u32, control_pressed: true })
122                    .unwrap();
123
124            let expected = String::from_utf8(vec![i]).unwrap();
125            assert_eq!(result, expected);
126        }
127    }
128
129    #[test]
130    fn convert_from_hid_usage_left() {
131        let result =
132            Option::<String>::from(HidUsage { hid_usage: HID_USAGE_KEY_LEFT, app_cursor: false })
133                .unwrap();
134        let expected = String::from("\u{1b}[D");
135        assert_eq!(result, expected);
136    }
137
138    #[test]
139    fn convert_from_hid_usage_left_app_cursor() {
140        let result =
141            Option::<String>::from(HidUsage { hid_usage: HID_USAGE_KEY_LEFT, app_cursor: true })
142                .unwrap();
143        let expected = String::from("\u{1b}OD");
144        assert_eq!(result, expected);
145    }
146}