test_keyboard.py 20 KB

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