1 #!/bin/env python3 2 # SPDX-License-Identifier: GPL-2.0 3 # -*- coding: utf-8 -*- 4 # 5 # Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com> 6 # Copyright (c) 2018 Red Hat, Inc. 7 # 8 9 from . import base 10 import hidtools.hid 11 import libevdev 12 import logging 13 14 logger = logging.getLogger("hidtools.test.keyboard") 15 16 17 class InvalidHIDCommunication(Exception): 18 pass 19 20 21 class KeyboardData(object): 22 pass 23 24 25 class BaseKeyboard(base.UHIDTestDevice): 26 def __init__(self, rdesc, name=None, input_info=None): 27 assert rdesc is not None 28 super().__init__(name, "Key", input_info=input_info, rdesc=rdesc) 29 self.keystates = {} 30 31 def _update_key_state(self, keys): 32 """ 33 Update the internal state of keys with the new state given. 34 35 :param key: a tuple of chars for the currently pressed keys. 36 """ 37 # First remove the already released keys 38 unused_keys = [k for k, v in self.keystates.items() if not v] 39 for key in unused_keys: 40 del self.keystates[key] 41 42 # self.keystates contains now the list of currently pressed keys, 43 # release them... 44 for key in self.keystates.keys(): 45 self.keystates[key] = False 46 47 # ...and press those that are in parameter 48 for key in keys: 49 self.keystates[key] = True 50 51 def _create_report_data(self): 52 keyboard = KeyboardData() 53 for key, value in self.keystates.items(): 54 key = key.replace(" ", "").lower() 55 setattr(keyboard, key, value) 56 return keyboard 57 58 def create_array_report(self, keys, reportID=None, application=None): 59 """ 60 Return an input report for this device. 61 62 :param keys: a tuple of chars for the pressed keys. The class maintains 63 the list of currently pressed keys, so to release a key, the caller 64 needs to call again this function without the key in this tuple. 65 :param reportID: the numeric report ID for this report, if needed 66 """ 67 self._update_key_state(keys) 68 reportID = reportID or self.default_reportID 69 70 keyboard = self._create_report_data() 71 return self.create_report(keyboard, reportID=reportID, application=application) 72 73 def event(self, keys, reportID=None, application=None): 74 """ 75 Send an input event on the default report ID. 76 77 :param keys: a tuple of chars for the pressed keys. The class maintains 78 the list of currently pressed keys, so to release a key, the caller 79 needs to call again this function without the key in this tuple. 80 """ 81 r = self.create_array_report(keys, reportID, application) 82 self.call_input_event(r) 83 return [r] 84 85 86 class PlainKeyboard(BaseKeyboard): 87 # fmt: off 88 report_descriptor = [ 89 0x05, 0x01, # Usage Page (Generic Desktop) 90 0x09, 0x06, # Usage (Keyboard) 91 0xa1, 0x01, # Collection (Application) 92 0x85, 0x01, # .Report ID (1) 93 0x05, 0x07, # .Usage Page (Keyboard) 94 0x19, 0xe0, # .Usage Minimum (224) 95 0x29, 0xe7, # .Usage Maximum (231) 96 0x15, 0x00, # .Logical Minimum (0) 97 0x25, 0x01, # .Logical Maximum (1) 98 0x75, 0x01, # .Report Size (1) 99 0x95, 0x08, # .Report Count (8) 100 0x81, 0x02, # .Input (Data,Var,Abs) 101 0x19, 0x00, # .Usage Minimum (0) 102 0x29, 0x97, # .Usage Maximum (151) 103 0x15, 0x00, # .Logical Minimum (0) 104 0x25, 0x01, # .Logical Maximum (1) 105 0x75, 0x01, # .Report Size (1) 106 0x95, 0x98, # .Report Count (152) 107 0x81, 0x02, # .Input (Data,Var,Abs) 108 0xc0, # End Collection 109 ] 110 # fmt: on 111 112 def __init__(self, rdesc=report_descriptor, name=None, input_info=None): 113 super().__init__(rdesc, name, input_info) 114 self.default_reportID = 1 115 116 117 class ArrayKeyboard(BaseKeyboard): 118 # fmt: off 119 report_descriptor = [ 120 0x05, 0x01, # Usage Page (Generic Desktop) 121 0x09, 0x06, # Usage (Keyboard) 122 0xa1, 0x01, # Collection (Application) 123 0x05, 0x07, # .Usage Page (Keyboard) 124 0x19, 0xe0, # .Usage Minimum (224) 125 0x29, 0xe7, # .Usage Maximum (231) 126 0x15, 0x00, # .Logical Minimum (0) 127 0x25, 0x01, # .Logical Maximum (1) 128 0x75, 0x01, # .Report Size (1) 129 0x95, 0x08, # .Report Count (8) 130 0x81, 0x02, # .Input (Data,Var,Abs) 131 0x95, 0x06, # .Report Count (6) 132 0x75, 0x08, # .Report Size (8) 133 0x15, 0x00, # .Logical Minimum (0) 134 0x26, 0xa4, 0x00, # .Logical Maximum (164) 135 0x05, 0x07, # .Usage Page (Keyboard) 136 0x19, 0x00, # .Usage Minimum (0) 137 0x29, 0xa4, # .Usage Maximum (164) 138 0x81, 0x00, # .Input (Data,Arr,Abs) 139 0xc0, # End Collection 140 ] 141 # fmt: on 142 143 def __init__(self, rdesc=report_descriptor, name=None, input_info=None): 144 super().__init__(rdesc, name, input_info) 145 146 def _create_report_data(self): 147 data = KeyboardData() 148 array = [] 149 150 hut = hidtools.hut.HUT 151 152 # strip modifiers from the array 153 for k, v in self.keystates.items(): 154 # we ignore depressed keys 155 if not v: 156 continue 157 158 usage = hut[0x07].from_name[k].usage 159 if usage >= 224 and usage <= 231: 160 # modifier 161 setattr(data, k.lower(), 1) 162 else: 163 array.append(k) 164 165 # if array length is bigger than 6, report ErrorRollOver 166 if len(array) > 6: 167 array = ["ErrorRollOver"] * 6 168 169 data.keyboard = array 170 return data 171 172 173 class LEDKeyboard(ArrayKeyboard): 174 # fmt: off 175 report_descriptor = [ 176 0x05, 0x01, # Usage Page (Generic Desktop) 177 0x09, 0x06, # Usage (Keyboard) 178 0xa1, 0x01, # Collection (Application) 179 0x05, 0x07, # .Usage Page (Keyboard) 180 0x19, 0xe0, # .Usage Minimum (224) 181 0x29, 0xe7, # .Usage Maximum (231) 182 0x15, 0x00, # .Logical Minimum (0) 183 0x25, 0x01, # .Logical Maximum (1) 184 0x75, 0x01, # .Report Size (1) 185 0x95, 0x08, # .Report Count (8) 186 0x81, 0x02, # .Input (Data,Var,Abs) 187 0x95, 0x01, # .Report Count (1) 188 0x75, 0x08, # .Report Size (8) 189 0x81, 0x01, # .Input (Cnst,Arr,Abs) 190 0x95, 0x05, # .Report Count (5) 191 0x75, 0x01, # .Report Size (1) 192 0x05, 0x08, # .Usage Page (LEDs) 193 0x19, 0x01, # .Usage Minimum (1) 194 0x29, 0x05, # .Usage Maximum (5) 195 0x91, 0x02, # .Output (Data,Var,Abs) 196 0x95, 0x01, # .Report Count (1) 197 0x75, 0x03, # .Report Size (3) 198 0x91, 0x01, # .Output (Cnst,Arr,Abs) 199 0x95, 0x06, # .Report Count (6) 200 0x75, 0x08, # .Report Size (8) 201 0x15, 0x00, # .Logical Minimum (0) 202 0x26, 0xa4, 0x00, # .Logical Maximum (164) 203 0x05, 0x07, # .Usage Page (Keyboard) 204 0x19, 0x00, # .Usage Minimum (0) 205 0x29, 0xa4, # .Usage Maximum (164) 206 0x81, 0x00, # .Input (Data,Arr,Abs) 207 0xc0, # End Collection 208 ] 209 # fmt: on 210 211 def __init__(self, rdesc=report_descriptor, name=None, input_info=None): 212 super().__init__(rdesc, name, input_info) 213 214 215 # Some Primax manufactured keyboards set the Usage Page after having defined 216 # some local Usages. It relies on the fact that the specification states that 217 # Usages are to be concatenated with Usage Pages upon finding a Main item (see 218 # 6.2.2.8). This test covers this case. 219 class PrimaxKeyboard(ArrayKeyboard): 220 # fmt: off 221 report_descriptor = [ 222 0x05, 0x01, # Usage Page (Generic Desktop) 223 0x09, 0x06, # Usage (Keyboard) 224 0xA1, 0x01, # Collection (Application) 225 0x05, 0x07, # .Usage Page (Keyboard) 226 0x19, 0xE0, # .Usage Minimum (224) 227 0x29, 0xE7, # .Usage Maximum (231) 228 0x15, 0x00, # .Logical Minimum (0) 229 0x25, 0x01, # .Logical Maximum (1) 230 0x75, 0x01, # .Report Size (1) 231 0x95, 0x08, # .Report Count (8) 232 0x81, 0x02, # .Input (Data,Var,Abs) 233 0x75, 0x08, # .Report Size (8) 234 0x95, 0x01, # .Report Count (1) 235 0x81, 0x01, # .Input (Data,Var,Abs) 236 0x05, 0x08, # .Usage Page (LEDs) 237 0x19, 0x01, # .Usage Minimum (1) 238 0x29, 0x03, # .Usage Maximum (3) 239 0x75, 0x01, # .Report Size (1) 240 0x95, 0x03, # .Report Count (3) 241 0x91, 0x02, # .Output (Data,Var,Abs) 242 0x95, 0x01, # .Report Count (1) 243 0x75, 0x05, # .Report Size (5) 244 0x91, 0x01, # .Output (Constant) 245 0x15, 0x00, # .Logical Minimum (0) 246 0x26, 0xFF, 0x00, # .Logical Maximum (255) 247 0x19, 0x00, # .Usage Minimum (0) 248 0x2A, 0xFF, 0x00, # .Usage Maximum (255) 249 0x05, 0x07, # .Usage Page (Keyboard) 250 0x75, 0x08, # .Report Size (8) 251 0x95, 0x06, # .Report Count (6) 252 0x81, 0x00, # .Input (Data,Arr,Abs) 253 0xC0, # End Collection 254 ] 255 # fmt: on 256 257 def __init__(self, rdesc=report_descriptor, name=None, input_info=None): 258 super().__init__(rdesc, name, input_info) 259 260 261 class BaseTest: 262 class TestKeyboard(base.BaseTestCase.TestUhid): 263 def test_single_key(self): 264 """check for key reliability.""" 265 uhdev = self.uhdev 266 evdev = uhdev.get_evdev() 267 syn_event = self.syn_event 268 269 r = uhdev.event(["a and A"]) 270 expected = [syn_event] 271 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1)) 272 events = uhdev.next_sync_events() 273 self.debug_reports(r, uhdev, events) 274 self.assertInputEventsIn(expected, events) 275 assert evdev.value[libevdev.EV_KEY.KEY_A] == 1 276 277 r = uhdev.event([]) 278 expected = [syn_event] 279 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0)) 280 events = uhdev.next_sync_events() 281 self.debug_reports(r, uhdev, events) 282 self.assertInputEventsIn(expected, events) 283 assert evdev.value[libevdev.EV_KEY.KEY_A] == 0 284 285 def test_two_keys(self): 286 uhdev = self.uhdev 287 evdev = uhdev.get_evdev() 288 syn_event = self.syn_event 289 290 r = uhdev.event(["a and A", "q and Q"]) 291 expected = [syn_event] 292 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1)) 293 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1)) 294 events = uhdev.next_sync_events() 295 self.debug_reports(r, uhdev, events) 296 self.assertInputEventsIn(expected, events) 297 assert evdev.value[libevdev.EV_KEY.KEY_A] == 1 298 299 r = uhdev.event([]) 300 expected = [syn_event] 301 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0)) 302 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0)) 303 events = uhdev.next_sync_events() 304 self.debug_reports(r, uhdev, events) 305 self.assertInputEventsIn(expected, events) 306 assert evdev.value[libevdev.EV_KEY.KEY_A] == 0 307 assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0 308 309 r = uhdev.event(["c and C"]) 310 expected = [syn_event] 311 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1)) 312 events = uhdev.next_sync_events() 313 self.debug_reports(r, uhdev, events) 314 self.assertInputEventsIn(expected, events) 315 assert evdev.value[libevdev.EV_KEY.KEY_C] == 1 316 317 r = uhdev.event(["c and C", "Spacebar"]) 318 expected = [syn_event] 319 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1)) 320 events = uhdev.next_sync_events() 321 self.debug_reports(r, uhdev, events) 322 assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events 323 self.assertInputEventsIn(expected, events) 324 assert evdev.value[libevdev.EV_KEY.KEY_C] == 1 325 assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1 326 327 r = uhdev.event(["Spacebar"]) 328 expected = [syn_event] 329 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0)) 330 events = uhdev.next_sync_events() 331 self.debug_reports(r, uhdev, events) 332 assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events 333 self.assertInputEventsIn(expected, events) 334 assert evdev.value[libevdev.EV_KEY.KEY_C] == 0 335 assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1 336 337 r = uhdev.event([]) 338 expected = [syn_event] 339 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0)) 340 events = uhdev.next_sync_events() 341 self.debug_reports(r, uhdev, events) 342 self.assertInputEventsIn(expected, events) 343 assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0 344 345 def test_modifiers(self): 346 # ctrl-alt-del would be very nice :) 347 uhdev = self.uhdev 348 syn_event = self.syn_event 349 350 r = uhdev.event(["LeftControl", "LeftShift", "= and +"]) 351 expected = [syn_event] 352 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1)) 353 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1)) 354 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1)) 355 events = uhdev.next_sync_events() 356 self.debug_reports(r, uhdev, events) 357 self.assertInputEventsIn(expected, events) 358 359 360 class TestPlainKeyboard(BaseTest.TestKeyboard): 361 def create_device(self): 362 return PlainKeyboard() 363 364 def test_10_keys(self): 365 uhdev = self.uhdev 366 syn_event = self.syn_event 367 368 r = uhdev.event( 369 [ 370 "1 and !", 371 "2 and @", 372 "3 and #", 373 "4 and $", 374 "5 and %", 375 "6 and ^", 376 "7 and &", 377 "8 and *", 378 "9 and (", 379 "0 and )", 380 ] 381 ) 382 expected = [syn_event] 383 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1)) 384 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1)) 385 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1)) 386 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1)) 387 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1)) 388 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1)) 389 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1)) 390 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1)) 391 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1)) 392 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1)) 393 events = uhdev.next_sync_events() 394 self.debug_reports(r, uhdev, events) 395 self.assertInputEventsIn(expected, events) 396 397 r = uhdev.event([]) 398 expected = [syn_event] 399 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0)) 400 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0)) 401 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0)) 402 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0)) 403 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0)) 404 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0)) 405 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0)) 406 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0)) 407 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0)) 408 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0)) 409 events = uhdev.next_sync_events() 410 self.debug_reports(r, uhdev, events) 411 self.assertInputEventsIn(expected, events) 412 413 414 class TestArrayKeyboard(BaseTest.TestKeyboard): 415 def create_device(self): 416 return ArrayKeyboard() 417 418 def test_10_keys(self): 419 uhdev = self.uhdev 420 syn_event = self.syn_event 421 422 r = uhdev.event( 423 [ 424 "1 and !", 425 "2 and @", 426 "3 and #", 427 "4 and $", 428 "5 and %", 429 "6 and ^", 430 ] 431 ) 432 expected = [syn_event] 433 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1)) 434 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1)) 435 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1)) 436 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1)) 437 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1)) 438 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1)) 439 events = uhdev.next_sync_events() 440 441 self.debug_reports(r, uhdev, events) 442 self.assertInputEventsIn(expected, events) 443 444 # ErrRollOver 445 r = uhdev.event( 446 [ 447 "1 and !", 448 "2 and @", 449 "3 and #", 450 "4 and $", 451 "5 and %", 452 "6 and ^", 453 "7 and &", 454 "8 and *", 455 "9 and (", 456 "0 and )", 457 ] 458 ) 459 events = uhdev.next_sync_events() 460 461 self.debug_reports(r, uhdev, events) 462 463 assert len(events) == 0 464 465 r = uhdev.event([]) 466 expected = [syn_event] 467 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0)) 468 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0)) 469 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0)) 470 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0)) 471 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0)) 472 expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0)) 473 events = uhdev.next_sync_events() 474 self.debug_reports(r, uhdev, events) 475 self.assertInputEventsIn(expected, events) 476 477 478 class TestLEDKeyboard(BaseTest.TestKeyboard): 479 def create_device(self): 480 return LEDKeyboard() 481 482 483 class TestPrimaxKeyboard(BaseTest.TestKeyboard): 484 def create_device(self): 485 return PrimaxKeyboard()
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.