Python Library Keyboard
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. keyboard (Keyboard Module)
  6. ==========================
  7. **Author:**
  8. * Dirk Alders <sudo-dirk@mount-mockery.de>
  9. **Description:**
  10. This Module supports functions and classes for collecting keyboard strokes.
  11. **Submodules:**
  12. * :class:`keyboard.keyboard`
  13. * :class:`keyboard.keyboard_csp`
  14. **Unittest:**
  15. See also the :download:`unittest <../../keyboard/_testresults_/unittest.pdf>` documentation.
  16. """
  17. __DEPENDENCIES__ = ['stringtools', 'task']
  18. import stringtools
  19. import task
  20. import evdev
  21. import logging
  22. import select
  23. import sys
  24. import time
  25. try:
  26. from config import APP_NAME as ROOT_LOGGER_NAME
  27. except ImportError:
  28. ROOT_LOGGER_NAME = 'root'
  29. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  30. __DESCRIPTION__ = """The Module {\\tt %s} is designed to fetch data from a keyboatd (e.g. RFID-Reader).
  31. For more Information read the sphinx documentation.""" % __name__.replace('_', '\_')
  32. """The Module Description"""
  33. __INTERPRETER__ = (2, 3)
  34. """The Tested Interpreter-Versions"""
  35. class keyboard(object):
  36. LOG_PREFIX = 'KBD:'
  37. WARN_MSG_INIT_DEVICES = 1
  38. WARN_MSG_RUNTIME = 2
  39. LEFT_SHIFT = 42
  40. RIGHT_SHIFT = 54
  41. CAPS_LOCK = 58
  42. NUM_LOCK = 69
  43. SCROLL_LOCK = 70
  44. STATE_BITS_LEFT = {
  45. LEFT_SHIFT: 1,
  46. CAPS_LOCK: 2, # CAPS LOCK
  47. 29: 4, # Left Ctrl
  48. 56: 8, # Alt
  49. 125: 32, # Left Windows
  50. NUM_LOCK: 64, # Num Lock
  51. SCROLL_LOCK: 128, # Scroll Lock
  52. }
  53. STATE_BITS_RIGHT = {
  54. RIGHT_SHIFT: 1,
  55. 97: 4, # Right Ctrl
  56. 100: 16, # Alt Gr
  57. 126: 32, # Right Windows
  58. }
  59. SCANCODES = {
  60. # Scancode: ASCIICode
  61. 2: u'1', 3: u'2', 4: u'3', 5: u'4', 6: u'5', 7: u'6', 8: u'7', 9: u'8', 10: u'9', 11: u'0', 12: u'-', 13: u'=',
  62. 16: u'q', 17: u'w', 18: u'e', 19: u'r', 20: u't', 21: u'y', 22: u'u', 23: u'i', 24: u'o', 25: u'p', 26: u'[', 27: u']',
  63. 28: u'\n', 30: u'a', 31: u's', 32: u'd', 33: u'f', 34: u'g', 35: u'h', 36: u'j', 37: u'k', 38: u'l', 39: u';', 40: u'"',
  64. 41: u'`', 43: u'\\', 44: u'z', 45: u'x', 46: u'c', 47: u'v', 48: u'b', 49: u'n', 50: u'm', 51: u',', 52: u'.', 53: u'/',
  65. 57: u' '
  66. }
  67. CAPSCODES = {
  68. 2: u'!', 3: u'@', 4: u'#', 5: u'$', 6: u'%', 7: u'^', 8: u'&', 9: u'*', 10: u'(', 11: u')', 12: u'_', 13: u'+',
  69. 16: u'Q', 17: u'W', 18: u'E', 19: u'R', 20: u'T', 21: u'Y', 22: u'U', 23: u'I', 24: u'O', 25: u'P', 26: u'{', 27: u'}',
  70. 30: u'A', 31: u'S', 32: u'D', 33: u'F', 34: u'G', 35: u'H', 36: u'J', 37: u'K', 38: u'L', 39: u':', 40: u'\'', 41: u'~',
  71. 43: u'|', 44: u'Z', 45: u'X', 46: u'C', 47: u'V', 48: u'B', 49: u'N', 50: u'M', 51: u'<', 52: u'>', 53: u'?',
  72. 57: u' '
  73. }
  74. CAPSKEYS = list(range(16, 26)) + list(range(30, 39)) + list(range(44, 51))
  75. def __init__(self, device_list):
  76. if sys.version_info >= (3, 0):
  77. str_types = [str]
  78. else:
  79. str_types = [str, unicode]
  80. if type(device_list) in str_types:
  81. device_list = [device_list]
  82. self.__device_list__ = []
  83. for device in device_list:
  84. if device is not None:
  85. self.__device_list__.append(device)
  86. self.__devices__ = None
  87. self.__last_warn_message__ = None
  88. #
  89. self.__init_rx_buffer__()
  90. #
  91. self.__data_available_callback__ = {}
  92. #
  93. self.__state_byte__ = 0
  94. self.__state_byte_left__ = 0
  95. self.__state_byte_right__ = 0
  96. self.__rx_queue__ = task.threaded_queue()
  97. self.__rx_queue__.enqueue(5, self.__receive_task__)
  98. self.__rx_queue__.run()
  99. self.__cb_queue__ = task.threaded_queue()
  100. self.__cb_queue__.run()
  101. def __init_rx_buffer__(self):
  102. self.__rx_data__ = {}
  103. for device in self.__device_list__:
  104. if device is not None:
  105. self.__rx_data__[device] = ''
  106. def __calc_keyboard_lock_state__(self, device):
  107. leds = device.leds()
  108. if evdev.ecodes.LED_NUML in leds:
  109. self.__state_byte_left__ |= self.STATE_BITS_LEFT[self.NUM_LOCK]
  110. else:
  111. self.__state_byte_left__ &= ~self.STATE_BITS_LEFT[self.NUM_LOCK]
  112. if evdev.ecodes.LED_CAPSL in leds:
  113. self.__state_byte_left__ |= self.STATE_BITS_LEFT[self.CAPS_LOCK]
  114. else:
  115. self.__state_byte_left__ &= ~self.STATE_BITS_LEFT[self.CAPS_LOCK]
  116. if evdev.ecodes.LED_SCROLLL in leds:
  117. self.__state_byte_left__ |= self.STATE_BITS_LEFT[self.SCROLL_LOCK]
  118. else:
  119. self.__state_byte_left__ &= ~self.STATE_BITS_LEFT[self.SCROLL_LOCK]
  120. self.__state_byte__ = self.__state_byte_left__ | self.__state_byte_right__
  121. def __calc_keyboard_state__(self, cevent):
  122. if cevent.scancode in self.STATE_BITS_LEFT:
  123. if cevent.scancode not in [self.CAPS_LOCK, self.NUM_LOCK, self.SCROLL_LOCK]:
  124. if cevent.keystate == 1:
  125. self.__state_byte_left__ |= self.STATE_BITS_LEFT[cevent.scancode]
  126. elif cevent.keystate == 0:
  127. self.__state_byte_left__ &= ~self.STATE_BITS_LEFT[cevent.scancode]
  128. return True
  129. elif cevent.scancode in self.STATE_BITS_RIGHT:
  130. if cevent.keystate == 1:
  131. self.__state_byte_right__ |= self.STATE_BITS_RIGHT[cevent.scancode]
  132. elif cevent.keystate == 0:
  133. self.__state_byte_right__ &= ~self.STATE_BITS_RIGHT[cevent.scancode]
  134. return True
  135. else:
  136. return False
  137. def __get_ascii__(self, scancode):
  138. shift = self.__state_byte__ & self.STATE_BITS_LEFT[self.LEFT_SHIFT]
  139. caps = self.__state_byte__ & self.STATE_BITS_LEFT[self.CAPS_LOCK]
  140. if not caps and not shift:
  141. return self.SCANCODES.get(scancode, None)
  142. elif not caps and shift:
  143. return self.CAPSCODES.get(scancode, None)
  144. elif caps and shift:
  145. return self.SCANCODES.get(scancode, None)
  146. else:
  147. return self.CAPSCODES.get(scancode, None)
  148. def __append_rx_buffer__(self, device, c):
  149. self.__rx_data__[device.fn] += c
  150. if sys.version_info >= (3, 0):
  151. logger.debug('%s RX[%s] <- "%s"', self.LOG_PREFIX, device.fn, stringtools.hexlify(bytes(c, 'ascii')))
  152. else:
  153. logger.debug('%s RX[%s] <- "%s"', self.LOG_PREFIX, device.fn, stringtools.hexlify(c))
  154. return len(c) > 0
  155. def __parse_ascii__(self, cevent, device):
  156. self.__calc_keyboard_lock_state__(device)
  157. if self.__calc_keyboard_state__(cevent):
  158. pass
  159. else:
  160. if cevent.keystate == 1:
  161. c = self.__get_ascii__(cevent.scancode)
  162. if c is not None:
  163. self.__append_rx_buffer__(device, c)
  164. else:
  165. logger.warning('%s No character in dictionary for scancode %d', self.LOG_PREFIX, cevent.scancode)
  166. def register_callback(self, callback, device_name):
  167. """
  168. :param callback: The callback which will be executed, when data is available.
  169. :type callback: function
  170. This method sets the callback which is executed, if data is available. You need to execute :func:`receive` of the instance
  171. given with the first argument.
  172. .. note:: The :func:`callback` is executed with these arguments:
  173. :param self: This communication instance
  174. """
  175. self.__data_available_callback__[device_name] = callback
  176. def receive(self, device_name, timeout=1, num=None):
  177. rv = None
  178. tm = time.time()
  179. while (num is not None and len(self.__rx_data__.get(device_name, [])) < num) or (num is None and len(self.__rx_data__.get(device_name, [])) < 1):
  180. if time.time() > tm + timeout:
  181. logger.warning('%s TIMEOUT (%ss): Not enough data in buffer. Requested %s and buffer size is %d.', self.LOG_PREFIX, repr(timeout), repr(num or 'all'), len(self.__receive_buffer__))
  182. return None
  183. time.sleep(0.05)
  184. if num is None:
  185. rv = self.__rx_data__[device_name]
  186. else:
  187. rv = self.__rx_data__[device_name][:num]
  188. self.__rx_data__[device_name] = self.__rx_data__[device_name][len(rv):]
  189. return rv
  190. def __receive_task__(self, queue_inst):
  191. if self.__devices__ is None:
  192. # A mapping of file descriptors (integers) to InputDevice instances.
  193. try:
  194. self.__devices__ = map(evdev.InputDevice, self.__device_list__)
  195. self.__devices__ = {dev.fd: dev for dev in self.__devices__}
  196. logger.info('%s Listening to the following Devices: %s', self.LOG_PREFIX, repr(self.__device_list__))
  197. except OSError:
  198. self.__devices__ = None
  199. if self.__last_warn_message__ is not self.WARN_MSG_INIT_DEVICES:
  200. logger.warning('%s Error while initialising the devices: %s', self.LOG_PREFIX, repr(self.__device_list__))
  201. self.__last_warn_message__ = self.WARN_MSG_INIT_DEVICES
  202. else:
  203. r = select.select(self.__devices__, [], [])[0]
  204. for fd in r:
  205. try:
  206. for event in self.__devices__[fd].read():
  207. if event.type == evdev.ecodes.EV_KEY:
  208. if self.__devices__[fd].fn in self.__device_list__:
  209. self.__parse_ascii__(evdev.categorize(event), self.__devices__[fd])
  210. except IOError:
  211. if self.__last_warn_message__ is not self.WARN_MSG_RUNTIME:
  212. logger.warning('%s Error reading from device: %s', self.LOG_PREFIX, repr(self.__devices__[fd].fn))
  213. self.__devices__ = None
  214. self.__last_warn_message__ = self.WARN_MSG_RUNTIME
  215. # enqueue required callbacks
  216. for device_name in self.__device_list__:
  217. if len(self.__rx_data__.get(device_name, [])) > 0:
  218. self.__cb_queue__.enqueue(5, self.__callback_execution_queue__, device_name)
  219. #
  220. queue_inst.enqueue(5, self.__receive_task__)
  221. def __callback_execution_queue__(self, queue_inst, device_name):
  222. cb = self.__data_available_callback__.get(device_name)
  223. if cb is not None and len(self.__rx_data__.get(device_name, [])) > 0:
  224. logger.debug("%s Executing callback: %s(self, %s)", self.LOG_PREFIX, cb.__name__, repr(device_name))
  225. cb(self, device_name)
  226. if len(self.__rx_data__.get(device_name, [])) > 0:
  227. queue_inst.enqueue(5, self.__callback_execution_queue__, device_name)
  228. def close(self):
  229. """
  230. This method closes the active keyboard channel, if channel exists.
  231. """
  232. self.__rx_queue__.stop()
  233. self.__rx_queue__.join()
  234. self.__cb_queue__.stop()
  235. self.__cb_queue__.join()
  236. def __del__(self):
  237. self.close()
  238. class keyboard_csp(keyboard):
  239. """
  240. Class to gather keystrokes for specific devices. All data till \\\\n will be collected to a frame.
  241. :param device_list: The device to listen to.
  242. :type device_list: str or list
  243. :param rx_lvl: The log level for single keystrokes
  244. :type rx_lvl: int
  245. **Example:**
  246. .. literalinclude:: ../../keyboard/_examples_/keyboard_csp.py
  247. Will result to the following output (if user scans an rfid-tag and types "12345\\\\n"):
  248. .. literalinclude:: ../../keyboard/_examples_/keyboard_csp.log
  249. """
  250. LOG_PREFIX = 'KBD_CSP:'
  251. def __init_rx_buffer__(self):
  252. self.__rx_data__ = {}
  253. self.__csp__ = {}
  254. for device in self.__device_list__:
  255. if device is not None:
  256. self.__rx_data__[device] = []
  257. self.__csp__[device] = stringtools.csp.csp()
  258. def __append_rx_buffer__(self, device, c):
  259. if sys.version_info >= (3, 0):
  260. content = self.__csp__[device.fn].process(bytes(c, 'ascii'))
  261. else:
  262. content = self.__csp__[device.fn].process(str(c))
  263. if len(content) > 0:
  264. for msg in content:
  265. logger.debug('%s RX[%s] <- "%s"', self.LOG_PREFIX, device.fn, stringtools.hexlify(msg))
  266. self.__rx_data__[device.fn] += content
  267. return True
  268. else:
  269. return False
  270. def receive(self, device_name, timeout=1):
  271. return keyboard.receive(self, device_name, timeout=timeout, num=1)[0]