#!/bin/env python3
# SPDX-License-Identifier: GPL-2.0
# -*- coding: utf-8 -*-
#
# Copyright (c) 2018 Benjamin Tissoires <benjamin.tissoires@gmail.com>
# Copyright (c) 2018 Red Hat, Inc.
#
from . import base
import hidtools.hid
import libevdev
import logging
logger = logging.getLogger("hidtools.test.keyboard" )
class InvalidHIDCommunication(Exception):
pass
class KeyboardData(object):
pass
class BaseKeyboard(base.UHIDTestDevice):
def __init__(self, rdesc, name=None , input_info=None ):
assert rdesc is not None
super().__init__(name, "Key" , input_info=input_info, rdesc=rdesc)
self.keystates = {}
def _update_key_state(self, keys):
"" "
Update the internal state of keys with the new state given.
:param key: a tuple of chars for the currently pressed keys.
"" "
# First remove the already released keys
unused_keys = [k for k, v in self.keystates.items() if not v]
for key in unused_keys:
del self.keystates[key]
# self.keystates contains now the list of currently pressed keys,
# release them...
for key in self.keystates.keys():
self.keystates[key] = False
# ...and press those that are in parameter
for key in keys:
self.keystates[key] = True
def _create_report_data(self):
keyboard = KeyboardData()
for key, value in self.keystates.items():
key = key.replace(" " , "" ).lower()
setattr(keyboard, key, value)
return keyboard
def create_array_report(self, keys, reportID=None , application=None ):
"" "
Return an input report for this device.
:param keys: a tuple of chars for the pressed keys. The class maintains
the list of currently pressed keys, so to release a key, the caller
needs to call again this function without the key in this tuple.
:param reportID: the numeric report ID for this report, if needed
"" "
self._update_key_state(keys)
reportID = reportID or self.default_reportID
keyboard = self._create_report_data()
return self.create_report(keyboard, reportID=reportID, application=application)
def event(self, keys, reportID=None , application=None ):
"" "
Send an input event on the default report ID.
:param keys: a tuple of chars for the pressed keys. The class maintains
the list of currently pressed keys, so to release a key, the caller
needs to call again this function without the key in this tuple.
"" "
r = self.create_array_report(keys, reportID, application)
self.call_input_event(r)
return [r]
class PlainKeyboard(BaseKeyboard):
# fmt: off
report_descriptor = [
0 x05, 0 x01, # Usage Page (Generic Desktop)
0 x09, 0 x06, # Usage (Keyboard)
0 xa1, 0 x01, # Collection (Application)
0 x85, 0 x01, # .Report ID (1)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 xe0, # .Usage Minimum (224)
0 x29, 0 xe7, # .Usage Maximum (231)
0 x15, 0 x00, # .Logical Minimum (0)
0 x25, 0 x01, # .Logical Maximum (1)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x08, # .Report Count (8)
0 x81, 0 x02, # .Input (Data,Var,Abs)
0 x19, 0 x00, # .Usage Minimum (0)
0 x29, 0 x97, # .Usage Maximum (151)
0 x15, 0 x00, # .Logical Minimum (0)
0 x25, 0 x01, # .Logical Maximum (1)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x98, # .Report Count (152)
0 x81, 0 x02, # .Input (Data,Var,Abs)
0 xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None , input_info=None ):
super().__init__(rdesc, name, input_info)
self.default_reportID = 1
class ArrayKeyboard(BaseKeyboard):
# fmt: off
report_descriptor = [
0 x05, 0 x01, # Usage Page (Generic Desktop)
0 x09, 0 x06, # Usage (Keyboard)
0 xa1, 0 x01, # Collection (Application)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 xe0, # .Usage Minimum (224)
0 x29, 0 xe7, # .Usage Maximum (231)
0 x15, 0 x00, # .Logical Minimum (0)
0 x25, 0 x01, # .Logical Maximum (1)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x08, # .Report Count (8)
0 x81, 0 x02, # .Input (Data,Var,Abs)
0 x95, 0 x06, # .Report Count (6)
0 x75, 0 x08, # .Report Size (8)
0 x15, 0 x00, # .Logical Minimum (0)
0 x26, 0 xa4, 0 x00, # .Logical Maximum (164)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 x00, # .Usage Minimum (0)
0 x29, 0 xa4, # .Usage Maximum (164)
0 x81, 0 x00, # .Input (Data,Arr,Abs)
0 xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None , input_info=None ):
super().__init__(rdesc, name, input_info)
def _create_report_data(self):
data = KeyboardData()
array = []
hut = hidtools.hut.HUT
# strip modifiers from the array
for k, v in self.keystates.items():
# we ignore depressed keys
if not v:
continue
usage = hut[0 x07].from_name[k].usage
if usage >= 224 and usage <= 231 :
# modifier
setattr(data, k.lower(), 1 )
else :
array.append(k)
# if array length is bigger than 6, report ErrorRollOver
if len(array) > 6 :
array = ["ErrorRollOver" ] * 6
data.keyboard = array
return data
class LEDKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0 x05, 0 x01, # Usage Page (Generic Desktop)
0 x09, 0 x06, # Usage (Keyboard)
0 xa1, 0 x01, # Collection (Application)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 xe0, # .Usage Minimum (224)
0 x29, 0 xe7, # .Usage Maximum (231)
0 x15, 0 x00, # .Logical Minimum (0)
0 x25, 0 x01, # .Logical Maximum (1)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x08, # .Report Count (8)
0 x81, 0 x02, # .Input (Data,Var,Abs)
0 x95, 0 x01, # .Report Count (1)
0 x75, 0 x08, # .Report Size (8)
0 x81, 0 x01, # .Input (Cnst,Arr,Abs)
0 x95, 0 x05, # .Report Count (5)
0 x75, 0 x01, # .Report Size (1)
0 x05, 0 x08, # .Usage Page (LEDs)
0 x19, 0 x01, # .Usage Minimum (1)
0 x29, 0 x05, # .Usage Maximum (5)
0 x91, 0 x02, # .Output (Data,Var,Abs)
0 x95, 0 x01, # .Report Count (1)
0 x75, 0 x03, # .Report Size (3)
0 x91, 0 x01, # .Output (Cnst,Arr,Abs)
0 x95, 0 x06, # .Report Count (6)
0 x75, 0 x08, # .Report Size (8)
0 x15, 0 x00, # .Logical Minimum (0)
0 x26, 0 xa4, 0 x00, # .Logical Maximum (164)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 x00, # .Usage Minimum (0)
0 x29, 0 xa4, # .Usage Maximum (164)
0 x81, 0 x00, # .Input (Data,Arr,Abs)
0 xc0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None , input_info=None ):
super().__init__(rdesc, name, input_info)
# Some Primax manufactured keyboards set the Usage Page after having defined
# some local Usages. It relies on the fact that the specification states that
# Usages are to be concatenated with Usage Pages upon finding a Main item (see
# 6.2.2.8). This test covers this case.
class PrimaxKeyboard(ArrayKeyboard):
# fmt: off
report_descriptor = [
0 x05, 0 x01, # Usage Page (Generic Desktop)
0 x09, 0 x06, # Usage (Keyboard)
0 xA1, 0 x01, # Collection (Application)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x19, 0 xE0, # .Usage Minimum (224)
0 x29, 0 xE7, # .Usage Maximum (231)
0 x15, 0 x00, # .Logical Minimum (0)
0 x25, 0 x01, # .Logical Maximum (1)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x08, # .Report Count (8)
0 x81, 0 x02, # .Input (Data,Var,Abs)
0 x75, 0 x08, # .Report Size (8)
0 x95, 0 x01, # .Report Count (1)
0 x81, 0 x01, # .Input (Data,Var,Abs)
0 x05, 0 x08, # .Usage Page (LEDs)
0 x19, 0 x01, # .Usage Minimum (1)
0 x29, 0 x03, # .Usage Maximum (3)
0 x75, 0 x01, # .Report Size (1)
0 x95, 0 x03, # .Report Count (3)
0 x91, 0 x02, # .Output (Data,Var,Abs)
0 x95, 0 x01, # .Report Count (1)
0 x75, 0 x05, # .Report Size (5)
0 x91, 0 x01, # .Output (Constant)
0 x15, 0 x00, # .Logical Minimum (0)
0 x26, 0 xFF, 0 x00, # .Logical Maximum (255)
0 x19, 0 x00, # .Usage Minimum (0)
0 x2A, 0 xFF, 0 x00, # .Usage Maximum (255)
0 x05, 0 x07, # .Usage Page (Keyboard)
0 x75, 0 x08, # .Report Size (8)
0 x95, 0 x06, # .Report Count (6)
0 x81, 0 x00, # .Input (Data,Arr,Abs)
0 xC0, # End Collection
]
# fmt: on
def __init__(self, rdesc=report_descriptor, name=None , input_info=None ):
super().__init__(rdesc, name, input_info)
class BaseTest:
class TestKeyboard(base.BaseTestCase.TestUhid):
def test_single_key(self):
"" "check for key reliability." ""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["a and A" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
def test_two_keys(self):
uhdev = self.uhdev
evdev = uhdev.get_evdev()
syn_event = self.syn_event
r = uhdev.event(["a and A" , "q and Q" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_A, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_Q, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_A] == 0
assert evdev.value[libevdev.EV_KEY.KEY_Q] == 0
r = uhdev.event(["c and C" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
r = uhdev.event(["c and C" , "Spacebar" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.KEY_C) not in events
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 1
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
r = uhdev.event(["Spacebar" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_C, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE) not in events
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_C] == 0
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 1
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_SPACE, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
assert evdev.value[libevdev.EV_KEY.KEY_SPACE] == 0
def test_modifiers(self):
# ctrl-alt-del would be very nice :)
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(["LeftControl" , "LeftShift" , "= and +" ])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTCTRL, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_LEFTSHIFT, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_EQUAL, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestPlainKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return PlainKeyboard()
def test_10_keys(self):
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(
[
"1 and !" ,
"2 and @" ,
"3 and #",
"4 and $" ,
"5 and %" ,
"6 and ^" ,
"7 and &" ,
"8 and *" ,
"9 and (" ,
"0 and )" ,
]
)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_0, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_7, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_8, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_9, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestArrayKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return ArrayKeyboard()
def test_10_keys(self):
uhdev = self.uhdev
syn_event = self.syn_event
r = uhdev.event(
[
"1 and !" ,
"2 and @" ,
"3 and #",
"4 and $" ,
"5 and %" ,
"6 and ^" ,
]
)
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 1 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 1 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
# ErrRollOver
r = uhdev.event(
[
"1 and !" ,
"2 and @" ,
"3 and #",
"4 and $" ,
"5 and %" ,
"6 and ^" ,
"7 and &" ,
"8 and *" ,
"9 and (" ,
"0 and )" ,
]
)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
assert len(events) == 0
r = uhdev.event([])
expected = [syn_event]
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_1, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_2, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_3, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_4, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_5, 0 ))
expected.append(libevdev.InputEvent(libevdev.EV_KEY.KEY_6, 0 ))
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
self.assertInputEventsIn(expected, events)
class TestLEDKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return LEDKeyboard()
class TestPrimaxKeyboard(BaseTest.TestKeyboard):
def create_device(self):
return PrimaxKeyboard()
Messung V0.5 in Prozent C=93 H=80 G=86
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland