File Index
Files and directories
Sign in
FILE

test_keyboard.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/hid/tests/test_keyboard.py PYTHON
  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
  9from . import base
 10import hidtools.hid
 11import libevdev
 12import logging
 13
 14logger = logging.getLogger("hidtools.test.keyboard")
 15
 16
 17class InvalidHIDCommunication(Exception):
 18    pass
 19
 20
 21class KeyboardData(object):
 22    pass
 23
 24
 25class 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
 86class 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
117class 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
173class 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.
219class 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
261class 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
360class 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
414class 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
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()
~/ Read-only file preview.