1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! A Rust crate for translating keycodes based on Chrome's mapping of keys.
//!
//! Easily convert, generate, listen for, or map keycodes for Linux, Windows, Mac, USB, and
//! browsers! Includes a `struct` to manage the state of pressed keys and generate USB HID reports.
//! Can be used for `#![no_std]` crates.
//!
//! Source of keycodes data:
//! *   Repo: <https://chromium.googlesource.com/chromium/src.git>
//! *   File: <https://chromium.googlesource.com/chromium/src.git/+/master/ui/events/keycodes/dom/keycode_converter_data.inc>
//! *   Git commit: `2b6022954b9fb600f15e08002a148187f4f986da`
//!
//! # Example: get a key mapping
//!
//! ```
//! use keycode::{KeyMap, KeyMappingId};
//!
//! // Check the USB HID value of the "a" key
//! fn main() {
//!     let a = KeyMap::from(KeyMappingId::UsA);
//!     assert_eq!(a.usb, 0x0004);
//!     assert_eq!(a.evdev, 0x001e);
//!     assert_eq!(a.xkb, 0x0026);
//!     assert_eq!(a.win, 0x001e);
//!     assert_eq!(a.mac, 0x0000);
//! }
//! ```
//!
//! # Example: generate a USB HID report
//!
//! ```
//! use keycode::{KeyboardState, KeyMap, KeyMappingId, KeyState};
//!
//! // Press and release the "A" key
//! fn main() {
//!     // Generate a keyboard state with n-key rollover
//!     let mut keyboard_state = KeyboardState::new(None);
//!
//!     // Get key mappings
//!     let a = KeyMap::from(KeyMappingId::UsA);
//!     let shift = KeyMap::from(KeyMappingId::ShiftLeft);
//!
//!     // USB HID report for "no keys pressed"
//!     assert_eq!(keyboard_state.usb_input_report(), &[0; 8]);
//!
//!     // Press "shift" and "a" keys
//!     keyboard_state.update_key(a, KeyState::Pressed);
//!     keyboard_state.update_key(shift, KeyState::Pressed);
//!
//!     // USB HID report for "'A' is pressed"
//!     assert_eq!(
//!         keyboard_state.usb_input_report(),
//!         &[shift.modifier.unwrap().bits(), 0, a.usb as u8, 0, 0, 0, 0, 0]
//!     );
//!
//!     // Release "shift" and "a" keys
//!     keyboard_state.update_key(a, KeyState::Released);
//!     keyboard_state.update_key(shift, KeyState::Released);
//!
//!     // USB HID report for "no keys pressed"
//!     assert_eq!(keyboard_state.usb_input_report(), &[0; 8]);
//! }
//! ```

#![no_std]
#![deny(missing_docs)]

use arraydeque::ArrayDeque;
use arrayvec::ArrayVec;

use keycode_macro::parse_keycode_converter_data;

include!(concat!(env!("OUT_DIR"), "/keycode_converter.rs"));

/// State of any key, whether it is pressed or not
#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy)]
pub enum KeyState {
    /// Pressed key
    Pressed,
    /// Released key
    Released,
}

/// Max keys is 235, but this is the size of array used to manage state
pub const NUM_KEYS: usize = 256;

/// Keyboard state that helps manage pressed keys, rollover, and generating USB HID reports
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyboardState {
    key_rollover: Option<usize>,
    key_state: ArrayDeque<[Option<KeyMap>; NUM_KEYS]>,
    modifier_state: KeyModifiers,
    input_report: ArrayVec<[u8; NUM_KEYS]>,
}

impl<'a> KeyboardState {
    /// Create a new keyboard state
    pub fn new(key_rollover: Option<usize>) -> KeyboardState {
        KeyboardState {
            key_rollover,
            key_state: ArrayDeque::new(),
            modifier_state: KeyModifiers::empty(),
            input_report: ArrayVec::new(),
        }
    }

    /// Update the keyboard state with a key's new state
    pub fn update_key(self: &mut Self, key: KeyMap, state: KeyState) {
        match state {
            KeyState::Pressed => {
                if let Some(key_modifier) = key.modifier {
                    self.modifier_state.insert(key_modifier);
                    return;
                }

                // Already contains key
                if self.key_state.contains(&Some(key)) {
                    return;
                }

                // Key state can't store anymore keys
                if self.key_state.is_full() {
                    return;
                }

                // Key rollover limit is met
                if let Some(key_rollover) = self.key_rollover {
                    if self.key_state.len() >= key_rollover {
                        return;
                    }
                }

                // We check if the `key_state` is full above, so this should be safe.
                self.key_state.push_back(Some(key)).unwrap();
            }
            KeyState::Released => {
                if let Some(key_modifier) = key.modifier {
                    self.modifier_state.remove(key_modifier);
                    return;
                }

                if self.key_state.is_empty() {
                    return;
                }

                self.key_state.retain(|k| *k != Some(key));
            }
        }
    }

    /// Generate a USB HID report
    pub fn usb_input_report(self: &mut Self) -> &[u8] {
        let mut input_report: ArrayVec<[u8; NUM_KEYS]> = ArrayVec::new();

        // Key modifiers
        input_report.push(self.modifier_state.bits());
        input_report.push(0);

        // Normal keys
        for possible_key in self.key_state.iter() {
            if let Some(key) = possible_key {
                input_report.push(key.usb as u8);
            }
        }

        // Default (not pressed)
        let min_input_report_size = self
            .key_rollover
            .and_then(|key_rollover_without_modifiers| Some(key_rollover_without_modifiers + 2))
            .unwrap_or(8);
        if input_report.len() < min_input_report_size {
            for _ in input_report.len()..min_input_report_size {
                input_report.push(0);
            }
        }

        self.input_report = input_report;
        self.input_report.as_slice()
    }
}