summaryrefslogtreecommitdiff
path: root/lulua/keyboard.py
diff options
context:
space:
mode:
Diffstat (limited to 'lulua/keyboard.py')
-rw-r--r--lulua/keyboard.py360
1 files changed, 59 insertions, 301 deletions
diff --git a/lulua/keyboard.py b/lulua/keyboard.py
index cf96efc..eed58a7 100644
--- a/lulua/keyboard.py
+++ b/lulua/keyboard.py
@@ -24,281 +24,16 @@ from typing import Text, Dict, Iterator, List
from .util import YamlLoader
-# XXX move this to keyboard.yaml?
-_buttonToXorgKeycode = {
- 'Bl1': 49,
- 'Bl2': 10,
- 'Bl3': 11,
- 'Bl4': 12,
- 'Bl5': 13,
- 'Bl6': 14,
- 'Bl7': 15,
- 'Br6': 16,
- 'Br5': 17,
- 'Br4': 18,
- 'Br3': 19,
- 'Br2': 20,
- 'Br1': 21,
- 'Br_bs': 22,
- 'Cl_tab': 23,
- 'Cl1': 24,
- 'Cl2': 25,
- 'Cl3': 26,
- 'Cl4': 27,
- 'Cl5': 28,
- 'Cr7': 29,
- 'Cr6': 30,
- 'Cr5': 31,
- 'Cr4': 32,
- 'Cr3': 33,
- 'Cr2': 34,
- 'Cr1': 35,
- 'CD_ret': 36,
- 'Dl_caps': 66,
- 'Dl1': 38,
- 'Dl2': 39,
- 'Dl3': 40,
- 'Dl4': 41,
- 'Dl5': 42,
- 'Dr7': 43,
- 'Dr6': 44,
- 'Dr5': 45,
- 'Dr4': 46,
- 'Dr3': 47,
- 'Dr2': 48,
- 'Dr1': 51,
- 'El_shift': 50,
- 'El1': 94,
- 'El2': 52,
- 'El3': 53,
- 'El4': 54,
- 'El5': 55,
- 'El6': 56,
- 'Er5': 57,
- 'Er4': 58,
- 'Er3': 59,
- 'Er2': 60,
- 'Er1': 61,
- 'Er_shift': 62,
- 'Fl_ctrl': 37,
- 'Fl_win': 133,
- 'Fl_alt': 64,
- 'Fl_space': 65,
- 'Fr_space': 65,
- 'Fr_altgr': 108,
- 'Fr_win': 105,
- 'Fr_menu': 135,
- 'Fr_ctrl': 105,
- }
-
-_buttonToKeyman = {
- 'Bl1': 'K_BKSLASH',
- 'Bl2': 'K_1',
- 'Bl3': 'K_2',
- 'Bl4': 'K_3',
- 'Bl5': 'K_4',
- 'Bl6': 'K_5',
- 'Bl7': 'K_6',
- 'Br6': 'K_7',
- 'Br5': 'K_8',
- 'Br4': 'K_9',
- 'Br3': 'K_0',
- 'Br2': 'K_LBRKT',
- 'Br1': 'K_RBRKT',
- 'Br_bs': 'K_BKSP',
- 'Cl_tab': 'K_TAB',
- 'Cl1': 'K_Q',
- 'Cl2': 'K_W',
- 'Cl3': 'K_E',
- 'Cl4': 'K_R',
- 'Cl5': 'K_T',
- 'Cr7': 'K_Z',
- 'Cr6': 'K_U',
- 'Cr5': 'K_I',
- 'Cr4': 'K_O',
- 'Cr3': 'K_P',
- 'Cr2': 'K_COLON',
- 'Cr1': 'K_EQUAL',
- 'CD_ret': 'K_ENTER',
- 'Dl_caps': 'CAPS',
- 'Dl1': 'K_A',
- 'Dl2': 'K_S',
- 'Dl3': 'K_D',
- 'Dl4': 'K_F',
- 'Dl5': 'K_G',
- 'Dr7': 'K_H',
- 'Dr6': 'K_J',
- 'Dr5': 'K_K',
- 'Dr4': 'K_L',
- 'Dr3': 'K_BKQUOTE',
- 'Dr2': 'K_QUOTE',
- 'Dr1': 'K_SLASH',
- 'El_shift': 'SHIFT', # XXX: there is no distinction between left/right
- 'El1': 'K_oE2',
- 'El2': 'K_Y',
- 'El3': 'K_X',
- 'El4': 'K_C',
- 'El5': 'K_V',
- 'El6': 'K_B',
- 'Er5': 'K_N',
- 'Er4': 'K_M',
- 'Er3': 'K_COMMA',
- 'Er2': 'K_PERIOD',
- 'Er1': 'K_HYPHEN',
- 'Er_shift': 'SHIFT',
- 'Fl_ctrl': 'LCTRL',
- 'Fl_win': 'K_?5B',
- 'Fl_alt': 'LALT',
- 'Fl_space': 'K_SPACE',
- 'Fr_space': 'K_SPACE',
- 'Fr_altgr': 'RALT',
- 'Fr_win': 'K_?5C',
- 'Fr_menu': 'K_?5D',
- 'Fr_ctrl': 'RCTRL',
- }
-
-# button windows scancode. See Keyboard Scan Code Specification Revision 1.3a
-# (published in 2000) from the Windows Platform Design Notes for example.
-_buttonToWinScancode = {
- 'Bl1': (0x29, ),
- 'Bl2': (0x02, ),
- 'Bl3': (0x03, ),
- 'Bl4': (0x04, ),
- 'Bl5': (0x05, ),
- 'Bl6': (0x06, ),
- 'Bl7': (0x07, ),
- 'Br6': (0x08, ),
- 'Br5': (0x09, ),
- 'Br4': (0x0A, ),
- 'Br3': (0x0B, ),
- 'Br2': (0x0C, ),
- 'Br1': (0x0D, ),
- 'Br_bs': (0x0E, ),
- 'Cl_tab': (0x0F, ),
- 'Cl1': (0x10, ),
- 'Cl2': (0x11, ),
- 'Cl3': (0x12, ),
- 'Cl4': (0x13, ),
- 'Cl5': (0x14, ),
- 'Cr7': (0x15, ),
- 'Cr6': (0x16, ),
- 'Cr5': (0x17, ),
- 'Cr4': (0x18, ),
- 'Cr3': (0x19, ),
- 'Cr2': (0x1A, ),
- 'Cr1': (0x1B, ),
- 'CD_ret': (0x1C, ),
- 'Dl_caps': (0x3A, ),
- 'Dl1': (0x1E, ),
- 'Dl2': (0x1F, ),
- 'Dl3': (0x20, ),
- 'Dl4': (0x21, ),
- 'Dl5': (0x22, ),
- 'Dr7': (0x23, ),
- 'Dr6': (0x24, ),
- 'Dr5': (0x25, ),
- 'Dr4': (0x26, ),
- 'Dr3': (0x27, ),
- 'Dr2': (0x28, ),
- 'Dr1': (0x2B, ),
- 'El_shift': (0x2A, ),
- 'El1': (0x56, ),
- 'El2': (0x2C, ),
- 'El3': (0x2D, ),
- 'El4': (0x2E, ),
- 'El5': (0x2F, ),
- 'El6': (0x30, ),
- 'Er5': (0x31, ),
- 'Er4': (0x32, ),
- 'Er3': (0x33, ),
- 'Er2': (0x34, ),
- 'Er1': (0x35, ),
- 'Er_shift': (0x36, ),
- 'Fl_ctrl': (0x1D, ),
- 'Fl_win': (0xe0, 0x5B, ),
- 'Fl_alt': (0x38, ),
- 'Fl_space': (0x39, ),
- 'Fr_space': (0x39, ),
- 'Fr_altgr': (0xe0, 0x38, ),
- 'Fr_win': (0xe0, 0x5C, ),
- 'Fr_menu': (0xe0, 0x5D, ),
- 'Fr_ctrl': (0xe0, 0x1D, ),
- }
-
-# see https://eastmanreference.com/complete-list-of-applescript-key-codes
-_buttonToOsxKeycode = {
- 'Bl1': 50,
- 'Bl2': 18,
- 'Bl3': 19,
- 'Bl4': 20,
- 'Bl5': 21,
- 'Bl6': 23,
- 'Bl7': 22,
- 'Br6': 26,
- 'Br5': 28,
- 'Br4': 25,
- 'Br3': 29,
- 'Br2': 27,
- 'Br1': 24,
- 'Br_bs': 51,
- 'Cl_tab': 48,
- 'Cl1': 12,
- 'Cl2': 13,
- 'Cl3': 14,
- 'Cl4': 15,
- 'Cl5': 17,
- 'Cr7': 16,
- 'Cr6': 32,
- 'Cr5': 34,
- 'Cr4': 31,
- 'Cr3': 35,
- 'Cr2': 33,
- 'Cr1': 30,
- 'CD_ret': 36,
- 'Dl_caps': 57,
- 'Dl1': 0,
- 'Dl2': 1,
- 'Dl3': 2,
- 'Dl4': 3,
- 'Dl5': 5,
- 'Dr7': 4,
- 'Dr6': 38,
- 'Dr5': 40,
- 'Dr4': 37,
- 'Dr3': 41,
- 'Dr2': 39,
- #'Dr1': 51,
- 'El_shift': 57,
- #'El1': 6,
- 'El2': 6,
- 'El3': 7,
- 'El4': 8,
- 'El5': 9,
- 'El6': 11,
- 'Er5': 45,
- 'Er4': 46,
- 'Er3': 43,
- 'Er2': 47,
- 'Er1': 44,
- 'Er_shift': 60,
- 'Fl_ctrl': 59,
- 'Fl_win': 55,
- 'Fl_alt': 58,
- 'Fl_space': 49,
- 'Fr_space': 49,
- 'Fr_altgr': 61,
- 'Fr_win': 55,
- #'Fr_menu': ,
- #'Fr_ctrl': 105,
- }
-
class Button:
- __slots__ = ('width', 'isMarked', 'i')
+ """ A single physical button on the keyboard """
+
+ __slots__ = ('width', 'isMarked', 'i', 'scancode')
_idToName : Dict[int, Text] = {}
_nameToId : Dict[Text, int] = {}
_nextNameId = 0
+ serializedName = 'standard'
- def __init__ (self, name: Text, width: float = 1, isMarked: bool = False):
+ def __init__ (self, name: Text, width: float = 1, isMarked: bool = False, scancode = None):
# map names to integers for fast comparison/hashing
i = Button._nameToId.get (name)
if i is None:
@@ -310,8 +45,14 @@ class Button:
self.width = width
# marked with an haptic line, for better orientation
self.isMarked = isMarked
-
- def __repr__ (self):
+ # scancode map, although they are not all technically scancodes, they
+ # are some low-level representation of the physical key
+ self.scancode = scancode
+ # special case for windows
+ if self.scancode and 'windows' in self.scancode:
+ self.scancode['windows'] = tuple (self.scancode['windows'])
+
+ def __repr__ (self): # pragma: no cover
return f'Button({self.name!r}, {self.width}, {self.isMarked})'
def __eq__ (self, other):
@@ -326,25 +67,10 @@ class Button:
def name (self):
return Button._idToName[self.i]
- @property
- def xorgKeycode (self):
- return _buttonToXorgKeycode[self.name]
-
- @property
- def keymanCode (self):
- return _buttonToKeyman[self.name]
-
- @property
- def windowsScancode (self):
- return _buttonToWinScancode[self.name]
-
- @property
- def osxKeycode (self):
- return _buttonToOsxKeycode[self.name]
-
@classmethod
def deserialize (self, data: Dict):
- kindMap = {'standard': Button, 'letter': LetterButton, 'multi': MultiRowButton}
+ kindMap = dict (map (lambda x: (x.serializedName, x),
+ (Button, LetterButton, MultiRowButton)))
try:
kind = data['kind']
del data['kind']
@@ -352,15 +78,28 @@ class Button:
kind = 'standard'
return kindMap[kind] (**data)
+ def serialize (self):
+ d = dict (name=self.name, width=self.width, scancode=self.scancode)
+ if self.__class__ is not Button:
+ d['kind'] = self.serializedName
+ if self.isMarked:
+ d['isMarked'] = self.isMarked
+ # turn the tuple back into a list
+ if d['scancode'] and 'windows' in d['scancode']:
+ d['scancode']['windows'] = list (d['scancode']['windows'])
+ return d
+
class LetterButton (Button):
"""
A letter, number or symbol button, but not special keys like modifier, tab,
"""
- def __init__ (self, name, isMarked=False):
- super().__init__ (name, width=1, isMarked=isMarked)
+ serializedName = 'letter'
- def __repr__ (self):
+ def __init__ (self, name, width=1, isMarked=False, scancode=None):
+ super().__init__ (name, width=width, isMarked=isMarked, scancode=scancode)
+
+ def __repr__ (self): # pragma: no cover
return f'LetterButton({self.name!r}, {self.isMarked})'
class MultiRowButton (Button):
@@ -370,19 +109,26 @@ class MultiRowButton (Button):
"""
__slots__ = ('span', )
+ serializedName = 'multi'
- def __init__ (self, name, span, isMarked=False):
- super ().__init__ (name, width=1, isMarked=isMarked)
+ def __init__ (self, name, span, width=1, isMarked=False, scancode=None):
+ super ().__init__ (name, width=width, isMarked=isMarked, scancode=scancode)
self.span = span
- def __repr__ (self):
+ def __repr__ (self): # pragma: no cover
return f'MultiRowButton({self.name!r}, {self.span!r}, {self.isMarked!r})'
+ def serialize (self):
+ d = super ().serialize ()
+ d['span'] = self.span
+ return d
+
class PhysicalKeyboard:
- __slots__ = ('name', 'rows', '_buttonToRow')
+ __slots__ = ('name', 'description', 'rows', '_buttonToRow')
- def __init__ (self, name: Text, rows):
+ def __init__ (self, name: Text, description: Text, rows):
self.name = name
+ self.description = description
self.rows = rows
self._buttonToRow = dict ()
@@ -393,7 +139,7 @@ class PhysicalKeyboard:
def __iter__ (self):
return iter (self.rows)
- def __repr__ (self):
+ def __repr__ (self): # pragma: no cover
return f'<PhysicalKeyboard {self.name} with {len (self)} keys>'
def __len__ (self):
@@ -405,7 +151,7 @@ class PhysicalKeyboard:
for k in self.keys ():
if k.name == name:
return k
- raise AttributeError (f'{name} is not a valid button name')
+ raise KeyError (f'{name} is not a valid button name')
def keys (self) -> Iterator[Button]:
""" Iterate over all keys """
@@ -428,7 +174,19 @@ class PhysicalKeyboard:
for btn in r:
row[1].append (Button.deserialize (btn))
rows.append (row)
- return cls (data['name'], rows)
+ return cls (data['name'], data['description'], rows)
+
+ def serialize (self):
+ rows = []
+ for l, r in self.rows:
+ newRow = [[], []]
+ for btn in l:
+ newRow[0].append (btn.serialize ())
+ for btn in r:
+ newRow[1].append (btn.serialize ())
+ rows.append (newRow)
+ return dict (name=self.name, description=self.description, rows=rows)
-defaultKeyboards = YamlLoader ('data/keyboards', PhysicalKeyboard.deserialize)
+dataDirectory = 'data/keyboards'
+defaultKeyboards = YamlLoader (dataDirectory, PhysicalKeyboard.deserialize)