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()
486