486 lines · python
1#!/bin/env python32# SPDX-License-Identifier: GPL-2.03# -*- coding: utf-8 -*-4#5# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>6# Copyright (c) 2018 Red Hat, Inc.7#8 9from . import base10import hidtools.hid11import libevdev12import logging13 14logger = logging.getLogger("hidtools.test.keyboard")15 16 17class InvalidHIDCommunication(Exception):18 pass19 20 21class KeyboardData(object):22 pass23 24 25class BaseKeyboard(base.UHIDTestDevice):26 def __init__(self, rdesc, name=None, input_info=None):27 assert rdesc is not None28 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 keys38 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] = False46 47 # ...and press those that are in parameter48 for key in keys:49 self.keystates[key] = True50 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 keyboard57 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 maintains63 the list of currently pressed keys, so to release a key, the caller64 needs to call again this function without the key in this tuple.65 :param reportID: the numeric report ID for this report, if needed66 """67 self._update_key_state(keys)68 reportID = reportID or self.default_reportID69 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 maintains78 the list of currently pressed keys, so to release a key, the caller79 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 86class PlainKeyboard(BaseKeyboard):87 # fmt: off88 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 Collection109 ]110 # fmt: on111 112 def __init__(self, rdesc=report_descriptor, name=None, input_info=None):113 super().__init__(rdesc, name, input_info)114 self.default_reportID = 1115 116 117class ArrayKeyboard(BaseKeyboard):118 # fmt: off119 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 Collection140 ]141 # fmt: on142 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.HUT151 152 # strip modifiers from the array153 for k, v in self.keystates.items():154 # we ignore depressed keys155 if not v:156 continue157 158 usage = hut[0x07].from_name[k].usage159 if usage >= 224 and usage <= 231:160 # modifier161 setattr(data, k.lower(), 1)162 else:163 array.append(k)164 165 # if array length is bigger than 6, report ErrorRollOver166 if len(array) > 6:167 array = ["ErrorRollOver"] * 6168 169 data.keyboard = array170 return data171 172 173class LEDKeyboard(ArrayKeyboard):174 # fmt: off175 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 Collection208 ]209 # fmt: on210 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 defined216# some local Usages. It relies on the fact that the specification states that217# Usages are to be concatenated with Usage Pages upon finding a Main item (see218# 6.2.2.8). This test covers this case.219class PrimaxKeyboard(ArrayKeyboard):220 # fmt: off221 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 Collection254 ]255 # fmt: on256 257 def __init__(self, rdesc=report_descriptor, name=None, input_info=None):258 super().__init__(rdesc, name, input_info)259 260 261class BaseTest:262 class TestKeyboard(base.BaseTestCase.TestUhid):263 def test_single_key(self):264 """check for key reliability."""265 uhdev = self.uhdev266 evdev = uhdev.get_evdev()267 syn_event = self.syn_event268 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] == 1276 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] == 0284 285 def test_two_keys(self):286 uhdev = self.uhdev287 evdev = uhdev.get_evdev()288 syn_event = self.syn_event289 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] == 1298 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] == 0307 assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0308 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] == 1316 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 events323 self.assertInputEventsIn(expected, events)324 assert evdev.value[libevdev.EV_KEY.KEY_C] == 1325 assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1326 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 events333 self.assertInputEventsIn(expected, events)334 assert evdev.value[libevdev.EV_KEY.KEY_C] == 0335 assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1336 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] == 0344 345 def test_modifiers(self):346 # ctrl-alt-del would be very nice :)347 uhdev = self.uhdev348 syn_event = self.syn_event349 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 360class TestPlainKeyboard(BaseTest.TestKeyboard):361 def create_device(self):362 return PlainKeyboard()363 364 def test_10_keys(self):365 uhdev = self.uhdev366 syn_event = self.syn_event367 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 414class TestArrayKeyboard(BaseTest.TestKeyboard):415 def create_device(self):416 return ArrayKeyboard()417 418 def test_10_keys(self):419 uhdev = self.uhdev420 syn_event = self.syn_event421 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 # ErrRollOver445 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) == 0464 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 478class TestLEDKeyboard(BaseTest.TestKeyboard):479 def create_device(self):480 return LEDKeyboard()481 482 483class TestPrimaxKeyboard(BaseTest.TestKeyboard):484 def create_device(self):485 return PrimaxKeyboard()486