Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2024 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | //! Contains the KeyboardClassifier, that tries to identify whether an Input device is an |
| 18 | //! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device |
| 19 | //! in order to verify/change the inferred keyboard type. |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 20 | //! |
| 21 | //! Initial classification: |
| 22 | //! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad, |
| 23 | //! Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic |
| 24 | //! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic |
| 25 | //! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic |
| 26 | //! |
| 27 | //! On process keys: |
| 28 | //! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to |
| 29 | //! KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true) |
| 30 | //! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event |
| 31 | //! across multiple device connections in a time period, then change type to |
| 32 | //! KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic |
| 33 | //! (i.e. verified = false). |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 34 | |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 35 | use crate::data_store::DataStore; |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 36 | use crate::input::{DeviceId, InputDevice, KeyboardType}; |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 37 | use crate::keyboard_classification_config::CLASSIFIED_DEVICES; |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 38 | use crate::{DeviceClass, ModifierState}; |
| 39 | use std::collections::HashMap; |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 40 | |
| 41 | /// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic |
| 42 | /// keyboard or non-alphabetic keyboard |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 43 | pub struct KeyboardClassifier { |
| 44 | device_map: HashMap<DeviceId, KeyboardInfo>, |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 45 | data_store: DataStore, |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 46 | } |
| 47 | |
| 48 | struct KeyboardInfo { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 49 | device: InputDevice, |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 50 | keyboard_type: KeyboardType, |
| 51 | is_finalized: bool, |
| 52 | } |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 53 | |
| 54 | impl KeyboardClassifier { |
| 55 | /// Create a new KeyboardClassifier |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 56 | pub fn new(data_store: DataStore) -> Self { |
| 57 | Self { device_map: HashMap::new(), data_store } |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 58 | } |
| 59 | |
| 60 | /// Adds keyboard to KeyboardClassifier |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 61 | pub fn notify_keyboard_changed(&mut self, device: InputDevice) { |
| 62 | let (keyboard_type, is_finalized) = self.classify_keyboard(&device); |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 63 | self.device_map |
| 64 | .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized }); |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | /// Get keyboard type for a tracked keyboard in KeyboardClassifier |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 68 | pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType { |
| 69 | return if let Some(keyboard) = self.device_map.get(&device_id) { |
| 70 | keyboard.keyboard_type |
| 71 | } else { |
| 72 | KeyboardType::None |
| 73 | }; |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 74 | } |
| 75 | |
| 76 | /// Tells if keyboard type classification is finalized. Once finalized the classification can't |
| 77 | /// change until device is reconnected again. |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 78 | /// |
| 79 | /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or |
| 80 | /// allowlist that are explicitly categorized and won't change with future key events |
| 81 | pub fn is_finalized(&self, device_id: DeviceId) -> bool { |
| 82 | return if let Some(keyboard) = self.device_map.get(&device_id) { |
| 83 | keyboard.is_finalized |
| 84 | } else { |
| 85 | false |
| 86 | }; |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 87 | } |
| 88 | |
| 89 | /// Process a key event and change keyboard type if required. |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 90 | /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic |
| 91 | /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 92 | pub fn process_key( |
| 93 | &mut self, |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 94 | device_id: DeviceId, |
| 95 | evdev_code: i32, |
| 96 | modifier_state: ModifierState, |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 97 | ) { |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 98 | if let Some(keyboard) = self.device_map.get_mut(&device_id) { |
| 99 | // Ignore all key events with modifier state since they can be macro shortcuts used by |
| 100 | // some non-keyboard peripherals like TV remotes, game controllers, etc. |
| 101 | if modifier_state.bits() != 0 { |
| 102 | return; |
| 103 | } |
| 104 | if Self::is_alphabetic_key(&evdev_code) { |
| 105 | keyboard.keyboard_type = KeyboardType::Alphabetic; |
| 106 | keyboard.is_finalized = true; |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 107 | self.data_store.set_keyboard_type( |
| 108 | &keyboard.device.identifier.descriptor, |
| 109 | keyboard.keyboard_type, |
| 110 | keyboard.is_finalized, |
| 111 | ); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 116 | fn classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool) { |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 117 | // This should never happen but having keyboard device class is necessary to be classified |
| 118 | // as any type of keyboard. |
| 119 | if !device.classes.contains(DeviceClass::Keyboard) { |
| 120 | return (KeyboardType::None, true); |
| 121 | } |
| 122 | // Normal classification for internal and virtual keyboards |
| 123 | if !device.classes.contains(DeviceClass::External) |
| 124 | || device.classes.contains(DeviceClass::Virtual) |
| 125 | { |
| 126 | return if device.classes.contains(DeviceClass::AlphabeticKey) { |
| 127 | (KeyboardType::Alphabetic, true) |
| 128 | } else { |
| 129 | (KeyboardType::NonAlphabetic, true) |
| 130 | }; |
| 131 | } |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 132 | |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 133 | // Check in data store |
| 134 | if let Some((keyboard_type, is_finalized)) = |
| 135 | self.data_store.get_keyboard_type(&device.identifier.descriptor) |
| 136 | { |
| 137 | return (keyboard_type, is_finalized); |
| 138 | } |
| 139 | |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 140 | // Check in known device list for classification |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 141 | for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { |
| 142 | if device.identifier.vendor == *vendor && device.identifier.product == *product { |
| 143 | return (*keyboard_type, *is_finalized); |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 144 | } |
| 145 | } |
| 146 | |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 147 | // Any composite device with multiple device classes should be categorized as non-alphabetic |
| 148 | // keyboard initially |
| 149 | if device.classes.contains(DeviceClass::Touch) |
| 150 | || device.classes.contains(DeviceClass::Cursor) |
| 151 | || device.classes.contains(DeviceClass::MultiTouch) |
| 152 | || device.classes.contains(DeviceClass::ExternalStylus) |
| 153 | || device.classes.contains(DeviceClass::Touchpad) |
| 154 | || device.classes.contains(DeviceClass::Dpad) |
| 155 | || device.classes.contains(DeviceClass::Gamepad) |
| 156 | || device.classes.contains(DeviceClass::Switch) |
| 157 | || device.classes.contains(DeviceClass::Joystick) |
| 158 | || device.classes.contains(DeviceClass::RotaryEncoder) |
| 159 | { |
| 160 | // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the |
| 161 | // kernel, we no longer need to process key events to verify. |
| 162 | return ( |
| 163 | KeyboardType::NonAlphabetic, |
| 164 | !device.classes.contains(DeviceClass::AlphabeticKey), |
| 165 | ); |
| 166 | } |
| 167 | // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard |
| 168 | if device.classes.contains(DeviceClass::AlphabeticKey) { |
| 169 | (KeyboardType::Alphabetic, true) |
| 170 | } else { |
| 171 | // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the |
| 172 | // kernel, we no longer need to process key events to verify. |
| 173 | (KeyboardType::NonAlphabetic, true) |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | fn is_alphabetic_key(evdev_code: &i32) -> bool { |
| 178 | // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ]) |
| 179 | (16..=27).contains(evdev_code) |
| 180 | // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `) |
| 181 | || (30..=41).contains(evdev_code) |
| 182 | // Keyboard alphabetic row 3 (\ Z X C V B N M , . /) |
| 183 | || (43..=53).contains(evdev_code) |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | #[cfg(test)] |
| 188 | mod tests { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 189 | use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore}; |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 190 | use crate::input::{DeviceId, InputDevice, KeyboardType}; |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 191 | use crate::keyboard_classification_config::CLASSIFIED_DEVICES; |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 192 | use crate::keyboard_classifier::KeyboardClassifier; |
| 193 | use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier}; |
| 194 | |
| 195 | static DEVICE_ID: DeviceId = DeviceId(1); |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 196 | static SECOND_DEVICE_ID: DeviceId = DeviceId(2); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 197 | static KEY_A: i32 = 30; |
| 198 | static KEY_1: i32 = 2; |
| 199 | |
| 200 | #[test] |
| 201 | fn classify_external_alphabetic_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 202 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 203 | classifier.notify_keyboard_changed(create_device( |
| 204 | DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, |
| 205 | )); |
| 206 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); |
| 207 | assert!(classifier.is_finalized(DEVICE_ID)); |
| 208 | } |
| 209 | |
| 210 | #[test] |
| 211 | fn classify_external_non_alphabetic_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 212 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 213 | classifier |
| 214 | .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External)); |
| 215 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 216 | assert!(classifier.is_finalized(DEVICE_ID)); |
| 217 | } |
| 218 | |
| 219 | #[test] |
| 220 | fn classify_mouse_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 221 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 222 | classifier.notify_keyboard_changed(create_device( |
| 223 | DeviceClass::Keyboard |
| 224 | | DeviceClass::Cursor |
| 225 | | DeviceClass::AlphabeticKey |
| 226 | | DeviceClass::External, |
| 227 | )); |
| 228 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 229 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 230 | } |
| 231 | |
| 232 | #[test] |
| 233 | fn classify_touchpad_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 234 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 235 | classifier.notify_keyboard_changed(create_device( |
| 236 | DeviceClass::Keyboard |
| 237 | | DeviceClass::Touchpad |
| 238 | | DeviceClass::AlphabeticKey |
| 239 | | DeviceClass::External, |
| 240 | )); |
| 241 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 242 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 243 | } |
| 244 | |
| 245 | #[test] |
| 246 | fn classify_stylus_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 247 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 248 | classifier.notify_keyboard_changed(create_device( |
| 249 | DeviceClass::Keyboard |
| 250 | | DeviceClass::ExternalStylus |
| 251 | | DeviceClass::AlphabeticKey |
| 252 | | DeviceClass::External, |
| 253 | )); |
| 254 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 255 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 256 | } |
| 257 | |
| 258 | #[test] |
| 259 | fn classify_dpad_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 260 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 261 | classifier.notify_keyboard_changed(create_device( |
| 262 | DeviceClass::Keyboard |
| 263 | | DeviceClass::Dpad |
| 264 | | DeviceClass::AlphabeticKey |
| 265 | | DeviceClass::External, |
| 266 | )); |
| 267 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 268 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 269 | } |
| 270 | |
| 271 | #[test] |
| 272 | fn classify_joystick_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 273 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 274 | classifier.notify_keyboard_changed(create_device( |
| 275 | DeviceClass::Keyboard |
| 276 | | DeviceClass::Joystick |
| 277 | | DeviceClass::AlphabeticKey |
| 278 | | DeviceClass::External, |
| 279 | )); |
| 280 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 281 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 282 | } |
| 283 | |
| 284 | #[test] |
| 285 | fn classify_gamepad_pretending_as_keyboard() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 286 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 287 | classifier.notify_keyboard_changed(create_device( |
| 288 | DeviceClass::Keyboard |
| 289 | | DeviceClass::Gamepad |
| 290 | | DeviceClass::AlphabeticKey |
| 291 | | DeviceClass::External, |
| 292 | )); |
| 293 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 294 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 295 | } |
| 296 | |
| 297 | #[test] |
| 298 | fn reclassify_keyboard_on_alphabetic_key_event() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 299 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 300 | classifier.notify_keyboard_changed(create_device( |
| 301 | DeviceClass::Keyboard |
| 302 | | DeviceClass::Dpad |
| 303 | | DeviceClass::AlphabeticKey |
| 304 | | DeviceClass::External, |
| 305 | )); |
| 306 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 307 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 308 | |
| 309 | // on alphabetic key event |
| 310 | classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); |
| 311 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic); |
| 312 | assert!(classifier.is_finalized(DEVICE_ID)); |
| 313 | } |
| 314 | |
| 315 | #[test] |
| 316 | fn dont_reclassify_keyboard_on_non_alphabetic_key_event() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 317 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 318 | classifier.notify_keyboard_changed(create_device( |
| 319 | DeviceClass::Keyboard |
| 320 | | DeviceClass::Dpad |
| 321 | | DeviceClass::AlphabeticKey |
| 322 | | DeviceClass::External, |
| 323 | )); |
| 324 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 325 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 326 | |
| 327 | // on number key event |
| 328 | classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None); |
| 329 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 330 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 331 | } |
| 332 | |
| 333 | #[test] |
| 334 | fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 335 | let mut classifier = create_classifier(); |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 336 | classifier.notify_keyboard_changed(create_device( |
| 337 | DeviceClass::Keyboard |
| 338 | | DeviceClass::Dpad |
| 339 | | DeviceClass::AlphabeticKey |
| 340 | | DeviceClass::External, |
| 341 | )); |
| 342 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 343 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 344 | |
| 345 | classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn); |
| 346 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic); |
| 347 | assert!(!classifier.is_finalized(DEVICE_ID)); |
| 348 | } |
| 349 | |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 350 | #[test] |
| 351 | fn classify_known_devices() { |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 352 | let mut classifier = create_classifier(); |
| 353 | for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() { |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 354 | classifier |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 355 | .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product)); |
| 356 | assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type); |
| 357 | assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized); |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | #[test] |
| 362 | fn classify_previously_reclassified_devices() { |
| 363 | let test_reader_writer = TestFileReaderWriter::new(); |
| 364 | { |
| 365 | let mut classifier = |
| 366 | KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); |
| 367 | let device = create_device( |
| 368 | DeviceClass::Keyboard |
| 369 | | DeviceClass::Dpad |
| 370 | | DeviceClass::AlphabeticKey |
| 371 | | DeviceClass::External, |
| 372 | ); |
| 373 | classifier.notify_keyboard_changed(device); |
| 374 | classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None); |
| 375 | } |
| 376 | |
| 377 | // Re-create classifier and data store to mimic a reboot (but use the same file system |
| 378 | // reader writer) |
| 379 | { |
| 380 | let mut classifier = |
| 381 | KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone()))); |
| 382 | let device = InputDevice { |
| 383 | device_id: SECOND_DEVICE_ID, |
| 384 | identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), |
| 385 | classes: DeviceClass::Keyboard |
| 386 | | DeviceClass::Dpad |
| 387 | | DeviceClass::AlphabeticKey |
| 388 | | DeviceClass::External, |
| 389 | }; |
| 390 | classifier.notify_keyboard_changed(device); |
| 391 | assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic); |
| 392 | assert!(classifier.is_finalized(SECOND_DEVICE_ID)); |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | fn create_classifier() -> KeyboardClassifier { |
| 397 | KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new()))) |
| 398 | } |
| 399 | |
| 400 | fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier { |
| 401 | RustInputDeviceIdentifier { |
| 402 | name: "test_device".to_string(), |
| 403 | location: "location".to_string(), |
| 404 | unique_id: "unique_id".to_string(), |
| 405 | bus: 123, |
| 406 | vendor, |
| 407 | product, |
| 408 | version: 567, |
| 409 | descriptor: "descriptor".to_string(), |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 410 | } |
| 411 | } |
| 412 | |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 413 | fn create_device(classes: DeviceClass) -> InputDevice { |
| 414 | InputDevice { |
| 415 | device_id: DEVICE_ID, |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 416 | identifier: create_identifier(/* vendor= */ 234, /* product= */ 345), |
Vaibhav Devmurari | 2e73b2a | 2024-06-07 17:45:19 +0000 | [diff] [blame] | 417 | classes, |
| 418 | } |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 419 | } |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 420 | |
| 421 | fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice { |
| 422 | InputDevice { |
| 423 | device_id: DEVICE_ID, |
Vaibhav Devmurari | 099fb59 | 2024-06-26 14:26:30 +0000 | [diff] [blame] | 424 | identifier: create_identifier(vendor, product), |
Vaibhav Devmurari | a8359fc | 2024-06-10 20:01:25 +0000 | [diff] [blame] | 425 | classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External, |
| 426 | } |
| 427 | } |
Vaibhav Devmurari | e58ffb9 | 2024-05-22 17:38:25 +0000 | [diff] [blame] | 428 | } |