From b8346cd8fe1a8cdc4a28e6cd9d29e485b4b6183a Mon Sep 17 00:00:00 2001 From: Andrew Kay Date: Tue, 18 Jun 2019 21:32:24 -0500 Subject: [PATCH] Add support for keyboards with a single modifier release scan code and add initial 3483 keymap --- oec/controller.py | 8 +- oec/keyboard.py | 46 +++++++-- oec/keymap_3483.py | 223 +++++++++++++++++++++++++++++++++++++++++ oec/terminal.py | 10 +- tests/test_keyboard.py | 97 +++++++++++++++++- 5 files changed, 365 insertions(+), 19 deletions(-) create mode 100644 oec/keymap_3483.py diff --git a/oec/controller.py b/oec/controller.py index b99467f..89e23fd 100644 --- a/oec/controller.py +++ b/oec/controller.py @@ -143,6 +143,10 @@ class Controller: (key, modifiers, modifiers_changed) = self.terminal.keyboard.get_key(scan_code) + if self.logger.isEnabledFor(logging.DEBUG): + self.logger.debug((f'Keystroke detected: Scan Code = {scan_code}, ' + f'Key = {key}, Modifiers = {modifiers}')) + # Update the status line if modifiers have changed. if modifiers_changed: indicators = bytearray(1) @@ -154,10 +158,6 @@ class Controller: self.terminal.status_line.write(35, indicators) - if self.logger.isEnabledFor(logging.DEBUG): - self.logger.debug((f'Keystroke detected: Scan Code = {scan_code}, ' - f'Key = {key}, Modifiers = {modifiers}')) - if not key: return diff --git a/oec/keyboard.py b/oec/keyboard.py index 6952af1..bc23cdb 100644 --- a/oec/keyboard.py +++ b/oec/keyboard.py @@ -4,7 +4,7 @@ oec.keyboard """ from enum import IntEnum, IntFlag, unique, auto -from collections import namedtuple +from collections import namedtuple, Mapping class KeyboardModifiers(IntFlag): """Keyboard modifiers.""" @@ -295,12 +295,24 @@ class Keyboard: self.modifiers = KeyboardModifiers.NONE + self.single_modifier_release = True + + if isinstance(self.keymap.modifier_release, Mapping): + self.single_modifier_release = False + + self.modifier_release = False + def get_key(self, scan_code): """Map a scan code to key and update modifiers state.""" key = self.keymap.default.get(scan_code) - if self._apply_modifiers(scan_code, key): - return (key, self.modifiers, True) + original_modifiers = self.modifiers + + (is_modifier, is_modifier_release) = self._apply_modifiers(scan_code, key) + + if is_modifier: + return (key if not is_modifier_release else None, self.modifiers, + self.modifiers != original_modifiers) if self.modifiers.is_shift(): key = self.keymap.shift.get(scan_code) @@ -319,21 +331,35 @@ class Keyboard: return (key, self.modifiers, False) def _apply_modifiers(self, scan_code, key): - if scan_code in self.keymap.modifier_release: - released_key = self.keymap.modifier_release[scan_code] + # TODO: Consider detection, in single modifier release mode, of entering + # modififier release but the next keystroke not being a modifier... also + # consider a warning in the case where a release of an unset modifier or + # the setting of an already set modifier occurs. + if self.single_modifier_release and scan_code == self.keymap.modifier_release: + self.modifier_release = True + + return (True, None) + + if (self.single_modifier_release and self.modifier_release) or (not self.single_modifier_release and scan_code in self.keymap.modifier_release): + self.modifier_release = False + + if self.single_modifier_release: + released_key = key + else: + released_key = self.keymap.modifier_release[scan_code] modifier = KEY_MODIFIER_MAP.get(released_key) if modifier is None: - return False + return (False, None) # Ignore the release of the caps lock key as it acts as a toggle. if modifier.is_caps_lock(): - return False + return (True, True) self.modifiers &= ~modifier - return True + return (True, True) if key in KEY_MODIFIER_MAP: modifier = KEY_MODIFIER_MAP[key] @@ -343,9 +369,9 @@ class Keyboard: else: self.modifiers |= modifier - return True + return (True, False) - return False + return (False, None) def get_ascii_character_for_key(key): """Map a key to ASCII character.""" diff --git a/oec/keymap_3483.py b/oec/keymap_3483.py new file mode 100644 index 0000000..d73eec0 --- /dev/null +++ b/oec/keymap_3483.py @@ -0,0 +1,223 @@ +""" +oec.keymap_3483 +~~~~~~~~~~~~~~~ +""" + +from .keyboard import Key, Keymap + +KEYMAP_DEFAULT = { + # Function Keys + 7: Key.PF1, + 15: Key.PF2, + 23: Key.PF3, + 31: Key.PF4, + 39: Key.PF5, + 47: Key.PF6, + 55: Key.PF7, + 63: Key.PF8, + 71: Key.PF9, + 79: Key.PF10, + 86: Key.PF11, + 94: Key.PF12, + 8: Key.PF13, + 16: Key.PF14, + 24: Key.PF15, + 32: Key.PF16, + 40: Key.PF17, + 48: Key.PF18, + 56: Key.PF19, + 64: Key.PF20, + 72: Key.PF21, + 80: Key.PF22, + 87: Key.PF23, + 95: Key.PF24, + + # Control Keys + 5: Key.ATTN, + 6: Key.BLANK_1, + 4: Key.BLANK_2, + 12: None, # ERASE_INPUT + 3: Key.PRINT, + 11: Key.HELP, + 131: Key.BLANK_3, + 10: Key.PLAY, + 1: Key.SET_UP, + 9: Key.RECORD, + + # First Row + 14: Key.BACKTICK, + 22: Key.ONE, + 30: Key.TWO, + 38: Key.THREE, + 37: Key.FOUR, + 46: Key.FIVE, + 54: Key.SIX, + 61: Key.SEVEN, + 62: Key.EIGHT, + 70: Key.NINE, + 69: Key.ZERO, + 78: Key.MINUS, + 85: Key.EQUAL, + 102: Key.BACKSPACE, + + # Second Row + 13: Key.TAB, + 21: Key.LOWER_Q, + 29: Key.LOWER_W, + 36: Key.LOWER_E, + 45: Key.LOWER_R, + 44: Key.LOWER_T, + 53: Key.LOWER_Y, + 60: Key.LOWER_U, + 67: Key.LOWER_I, + 68: Key.LOWER_O, + 77: Key.LOWER_P, + 84: Key.CENT, + 91: Key.BACKSLASH, + 90: Key.FIELD_EXIT, + + # Third Row + 20: Key.CAPS_LOCK, + 28: Key.LOWER_A, + 27: Key.LOWER_S, + 35: Key.LOWER_D, + 43: Key.LOWER_F, + 52: Key.LOWER_G, + 51: Key.LOWER_H, + 59: Key.LOWER_J, + 66: Key.LOWER_K, + 75: Key.LOWER_L, + 76: Key.SEMICOLON, + 82: Key.SINGLE_QUOTE, + 83: Key.LEFT_BRACE, + + # Fourth Row + 18: Key.LEFT_SHIFT, + 19: Key.LESS, + 26: Key.LOWER_Z, + 34: Key.LOWER_X, + 33: Key.LOWER_C, + 42: Key.LOWER_V, + 50: Key.LOWER_B, + 49: Key.LOWER_N, + 58: Key.LOWER_M, + 65: Key.COMMA, + 73: Key.PERIOD, + 74: Key.SLASH, + 89: Key.RIGHT_SHIFT, + + # Bottom Row + 17: Key.RESET, + 25: Key.LEFT_ALT, + 41: Key.SPACE, + 57: Key.RIGHT_ALT, + 88: Key.ENTER, + + # Center + 103: Key.BACKTAB, + 110: Key.DUP, + 111: Key.BLANK_4, + 100: Key.NEWLINE, + 101: Key.INSERT, + 109: Key.DELETE, + + 99: Key.UP, + 97: Key.LEFT, + 98: None, # RULE + 106: Key.RIGHT, + 96: Key.DOWN, + + # Number Pad + 118: Key.NUMPAD_BLANK_1, + 119: Key.NUMPAD_BLANK_2, + 126: Key.NUMPAD_BLANK_3, + 132: Key.NUMPAD_BLANK_4, + 108: Key.NUMPAD_SEVEN, + 117: Key.NUMPAD_EIGHT, + 125: Key.NUMPAD_NINE, + 124: Key.NUMPAD_FIELD_MINUS, + 107: Key.NUMPAD_FOUR, + 115: Key.NUMPAD_FIVE, + 116: Key.NUMPAD_SIX, + 123: Key.NUMPAD_BLANK_5, + 105: Key.NUMPAD_ONE, + 114: Key.NUMPAD_TWO, + 122: Key.NUMPAD_THREE, + 121: Key.NUMPAD_FIELD_PLUS, + 112: Key.NUMPAD_ZERO, + 113: Key.NUMPAD_PERIOD +} + +KEYMAP_SHIFT = { + **KEYMAP_DEFAULT, + + # Control Keys + 5: Key.SYS_RQ, + 12: Key.ERASE_INPUT, + + # First Row + 14: Key.TILDE, + 22: Key.BAR, + 30: Key.AT, + 38: Key.HASH, + 37: Key.DOLLAR, + 46: Key.PERCENT, + 54: Key.NOT, + 61: Key.AMPERSAND, + 62: Key.ASTERISK, + 70: Key.LEFT_PAREN, + 69: Key.RIGHT_PAREN, + 78: Key.UNDERSCORE, + 85: Key.PLUS, + + # Second Row + 21: Key.UPPER_Q, + 29: Key.UPPER_W, + 36: Key.UPPER_E, + 45: Key.UPPER_R, + 44: Key.UPPER_T, + 53: Key.UPPER_Y, + 60: Key.UPPER_U, + 67: Key.UPPER_I, + 68: Key.UPPER_O, + 77: Key.UPPER_P, + 84: Key.EXCLAMATION, + 91: Key.BROKEN_BAR, + + # Third Row + 28: Key.UPPER_A, + 27: Key.UPPER_S, + 35: Key.UPPER_D, + 43: Key.UPPER_F, + 52: Key.UPPER_G, + 51: Key.UPPER_H, + 59: Key.UPPER_J, + 66: Key.UPPER_K, + 75: Key.UPPER_L, + 76: Key.COLON, + 82: Key.DOUBLE_QUOTE, + 83: Key.RIGHT_BRACE, + + # Fourth Row + 19: Key.GREATER, + 26: Key.UPPER_Z, + 34: Key.UPPER_X, + 33: Key.UPPER_C, + 42: Key.UPPER_V, + 50: Key.UPPER_B, + 49: Key.UPPER_N, + 58: Key.UPPER_M, + 65: Key.COMMA, # TODO: ??? + 73: Key.CENTER_PERIOD, # TODO: ??? + 74: Key.QUESTION, + + # Center + 99: Key.ROLL_UP, + 96: Key.ROLL_DOWN, +} + +KEYMAP_ALT = { + **KEYMAP_DEFAULT +} + +KEYMAP = Keymap('3483', KEYMAP_DEFAULT, KEYMAP_SHIFT, KEYMAP_ALT, modifier_release=240) diff --git a/oec/terminal.py b/oec/terminal.py index f7b8417..235adfc 100644 --- a/oec/terminal.py +++ b/oec/terminal.py @@ -7,7 +7,8 @@ from collections import namedtuple from .display import StatusLine from .keyboard import Keyboard -from .keymap_3278_2 import KEYMAP +from .keymap_3278_2 import KEYMAP as KEYMAP_3278_2 +from .keymap_3483 import KEYMAP as KEYMAP_3483 # Does not include the status line row. Dimensions = namedtuple('Dimensions', ['rows', 'columns']) @@ -28,7 +29,12 @@ def get_dimensions(terminal_id, extended_id): def get_keyboard(terminal_id, extended_id): """Get keyboard configured with terminal keymap.""" - return Keyboard(KEYMAP) + keymap = KEYMAP_3278_2 + + if extended_id == 'c1348300': + keymap = KEYMAP_3483 + + return Keyboard(keymap) class Terminal: """Terminal information, devices and helpers.""" diff --git a/tests/test_keyboard.py b/tests/test_keyboard.py index d76855d..a17bcb8 100644 --- a/tests/test_keyboard.py +++ b/tests/test_keyboard.py @@ -3,7 +3,8 @@ import unittest import context from oec.keyboard import KeyboardModifiers, Key, Keymap, Keyboard, get_ascii_character_for_key -from oec.keymap_3278_2 import KEYMAP +from oec.keymap_3278_2 import KEYMAP as KEYMAP_3278_2 +from oec.keymap_3483 import KEYMAP as KEYMAP_3483 class KeyboardModifiersTestCase(unittest.TestCase): def test_is_shift(self): @@ -36,9 +37,99 @@ class KeyboardModifiersTestCase(unittest.TestCase): with self.subTest(modifiers=input): self.assertFalse(modifiers.is_caps_lock()) -class KeyboardGetKeyTestCase(unittest.TestCase): +class KeyboardGetKeySingleModifierReleaseTestCase(unittest.TestCase): def setUp(self): - self.keyboard = Keyboard(KEYMAP) + self.keyboard = Keyboard(KEYMAP_3483) + + def test_single_modifier_release_is_true(self): + self.assertTrue(self.keyboard.single_modifier_release) + + def test_default(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(50, Key.LOWER_B, KeyboardModifiers.NONE, False) + self._assert_get_key(33, Key.LOWER_C, KeyboardModifiers.NONE, False) + self._assert_get_key(35, Key.LOWER_D, KeyboardModifiers.NONE, False) + + def test_shift(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(18, Key.LEFT_SHIFT, KeyboardModifiers.LEFT_SHIFT, True) + self._assert_get_key(50, Key.UPPER_B, KeyboardModifiers.LEFT_SHIFT, False) + self._assert_get_key(89, Key.RIGHT_SHIFT, KeyboardModifiers.LEFT_SHIFT | KeyboardModifiers.RIGHT_SHIFT, True) + self._assert_get_key(33, Key.UPPER_C, KeyboardModifiers.LEFT_SHIFT | KeyboardModifiers.RIGHT_SHIFT, False) + self._assert_get_key(240, None, KeyboardModifiers.LEFT_SHIFT | KeyboardModifiers.RIGHT_SHIFT, False) + self._assert_get_key(18, None, KeyboardModifiers.RIGHT_SHIFT, True) + self._assert_get_key(35, Key.UPPER_D, KeyboardModifiers.RIGHT_SHIFT, False) + self._assert_get_key(240, None, KeyboardModifiers.RIGHT_SHIFT, False) + self._assert_get_key(89, None, KeyboardModifiers.NONE, True) + self._assert_get_key(36, Key.LOWER_E, KeyboardModifiers.NONE, False) + + # TODO... include the additional ALT reset scan_code! + + def test_mapped_alt(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(57, Key.RIGHT_ALT, KeyboardModifiers.RIGHT_ALT, True) + self._assert_get_key(50, Key.LOWER_B, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(240, None, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(57, None, KeyboardModifiers.NONE, True) + self._assert_get_key(33, Key.LOWER_C, KeyboardModifiers.NONE, False) + + def test_unmapped_alt(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(57, Key.RIGHT_ALT, KeyboardModifiers.RIGHT_ALT, True) + self._assert_get_key(50, Key.LOWER_B, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(240, None, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(57, None, KeyboardModifiers.NONE, True) + self._assert_get_key(33, Key.LOWER_C, KeyboardModifiers.NONE, False) + + def test_alt_and_shift(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(57, Key.RIGHT_ALT, KeyboardModifiers.RIGHT_ALT, True) + self._assert_get_key(50, Key.LOWER_B, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(18, Key.LEFT_SHIFT, KeyboardModifiers.RIGHT_ALT | KeyboardModifiers.LEFT_SHIFT, True) + self._assert_get_key(33, Key.UPPER_C, KeyboardModifiers.RIGHT_ALT | KeyboardModifiers.LEFT_SHIFT, False) + self._assert_get_key(240, None, KeyboardModifiers.RIGHT_ALT | KeyboardModifiers.LEFT_SHIFT, False) + self._assert_get_key(18, None, KeyboardModifiers.RIGHT_ALT, True) + self._assert_get_key(35, Key.LOWER_D, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(240, None, KeyboardModifiers.RIGHT_ALT, False) + self._assert_get_key(57, None, KeyboardModifiers.NONE, True) + self._assert_get_key(36, Key.LOWER_E, KeyboardModifiers.NONE, False) + + def test_caps_lock(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(20, Key.CAPS_LOCK, KeyboardModifiers.CAPS_LOCK, True) + self._assert_get_key(240, None, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(20, None, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(50, Key.UPPER_B, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(20, Key.CAPS_LOCK, KeyboardModifiers.NONE, True) + self._assert_get_key(240, None, KeyboardModifiers.NONE, False) + self._assert_get_key(20, None, KeyboardModifiers.NONE, False) + self._assert_get_key(33, Key.LOWER_C, KeyboardModifiers.NONE, False) + + def test_caps_lock_and_shift(self): + self._assert_get_key(28, Key.LOWER_A, KeyboardModifiers.NONE, False) + self._assert_get_key(20, Key.CAPS_LOCK, KeyboardModifiers.CAPS_LOCK, True) + self._assert_get_key(240, None, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(20, None, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(50, Key.UPPER_B, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(18, Key.LEFT_SHIFT, KeyboardModifiers.CAPS_LOCK | KeyboardModifiers.LEFT_SHIFT, True) + self._assert_get_key(33, Key.LOWER_C, KeyboardModifiers.CAPS_LOCK | KeyboardModifiers.LEFT_SHIFT, False) + self._assert_get_key(240, None, KeyboardModifiers.CAPS_LOCK | KeyboardModifiers.LEFT_SHIFT, False) + self._assert_get_key(18, None, KeyboardModifiers.CAPS_LOCK, True) + self._assert_get_key(35, Key.UPPER_D, KeyboardModifiers.CAPS_LOCK, False) + self._assert_get_key(20, Key.CAPS_LOCK, KeyboardModifiers.NONE, True) + self._assert_get_key(240, None, KeyboardModifiers.NONE, False) + self._assert_get_key(20, None, KeyboardModifiers.NONE, False) + self._assert_get_key(36, Key.LOWER_E, KeyboardModifiers.NONE, False) + + def _assert_get_key(self, scan_code, key, modifiers, modifiers_changed): + self.assertEqual(self.keyboard.get_key(scan_code), (key, modifiers, modifiers_changed)) + +class KeyboardGetKeyMultipleModifierReleaseTestCase(unittest.TestCase): + def setUp(self): + self.keyboard = Keyboard(KEYMAP_3278_2) + + def test_single_modifier_release_is_false(self): + self.assertFalse(self.keyboard.single_modifier_release) def test_default(self): self._assert_get_key(96, Key.LOWER_A, KeyboardModifiers.NONE, False)