mirror of
https://github.com/lowobservable/oec.git
synced 2026-03-03 18:26:01 +00:00
Add support for keyboards with a single modifier release scan code and add initial 3483 keymap
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
223
oec/keymap_3483.py
Normal 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)
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user