Add support for keyboards with a single modifier release scan code and add initial 3483 keymap

This commit is contained in:
Andrew Kay
2019-06-18 21:32:24 -05:00
parent 1a8a107d8c
commit b8346cd8fe
5 changed files with 365 additions and 19 deletions

View File

@@ -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

View File

@@ -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."""

223
oec/keymap_3483.py Normal file
View File

@@ -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)

View File

@@ -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."""

View File

@@ -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)