summaryrefslogtreecommitdiff
path: root/lulua
diff options
context:
space:
mode:
Diffstat (limited to 'lulua')
-rw-r--r--lulua/data/keyboards/ibmpc105.yaml436
-rw-r--r--lulua/keyboard.py360
-rw-r--r--lulua/render.py19
-rw-r--r--lulua/test_keyboard.py18
4 files changed, 519 insertions, 314 deletions
diff --git a/lulua/data/keyboards/ibmpc105.yaml b/lulua/data/keyboards/ibmpc105.yaml
index d9dcb76..0938262 100644
--- a/lulua/data/keyboards/ibmpc105.yaml
+++ b/lulua/data/keyboards/ibmpc105.yaml
@@ -3,130 +3,558 @@ description: Standard IBM PC 105 key layout (European)
rows:
- - - kind: letter
name: Bl1
+ scancode:
+ keyman: K_BKSLASH
+ macos: 50
+ windows:
+ - 41
+ xorg: 49
+ width: 1
- kind: letter
name: Bl2
+ scancode:
+ keyman: K_1
+ macos: 18
+ windows:
+ - 2
+ xorg: 10
+ width: 1
- kind: letter
name: Bl3
+ scancode:
+ keyman: K_2
+ macos: 19
+ windows:
+ - 3
+ xorg: 11
+ width: 1
- kind: letter
name: Bl4
+ scancode:
+ keyman: K_3
+ macos: 20
+ windows:
+ - 4
+ xorg: 12
+ width: 1
- kind: letter
name: Bl5
+ scancode:
+ keyman: K_4
+ macos: 21
+ windows:
+ - 5
+ xorg: 13
+ width: 1
- kind: letter
name: Bl6
+ scancode:
+ keyman: K_5
+ macos: 23
+ windows:
+ - 6
+ xorg: 14
+ width: 1
- kind: letter
name: Bl7
+ scancode:
+ keyman: K_6
+ macos: 22
+ windows:
+ - 7
+ xorg: 15
+ width: 1
- - kind: letter
name: Br6
+ scancode:
+ keyman: K_7
+ macos: 26
+ windows:
+ - 8
+ xorg: 16
+ width: 1
- kind: letter
name: Br5
+ scancode:
+ keyman: K_8
+ macos: 28
+ windows:
+ - 9
+ xorg: 17
+ width: 1
- kind: letter
name: Br4
+ scancode:
+ keyman: K_9
+ macos: 25
+ windows:
+ - 10
+ xorg: 18
+ width: 1
- kind: letter
name: Br3
+ scancode:
+ keyman: K_0
+ macos: 29
+ windows:
+ - 11
+ xorg: 19
+ width: 1
- kind: letter
name: Br2
+ scancode:
+ keyman: K_LBRKT
+ macos: 27
+ windows:
+ - 12
+ xorg: 20
+ width: 1
- kind: letter
name: Br1
+ scancode:
+ keyman: K_RBRKT
+ macos: 24
+ windows:
+ - 13
+ xorg: 21
+ width: 1
- name: Br_bs
+ scancode:
+ keyman: K_BKSP
+ macos: 51
+ windows:
+ - 14
+ xorg: 22
width: 1.75
- - - name: Cl_tab
+ scancode:
+ keyman: K_TAB
+ macos: 48
+ windows:
+ - 15
+ xorg: 23
width: 1.75
- kind: letter
name: Cl1
+ scancode:
+ keyman: K_Q
+ macos: 12
+ windows:
+ - 16
+ xorg: 24
+ width: 1
- kind: letter
name: Cl2
+ scancode:
+ keyman: K_W
+ macos: 13
+ windows:
+ - 17
+ xorg: 25
+ width: 1
- kind: letter
name: Cl3
+ scancode:
+ keyman: K_E
+ macos: 14
+ windows:
+ - 18
+ xorg: 26
+ width: 1
- kind: letter
name: Cl4
+ scancode:
+ keyman: K_R
+ macos: 15
+ windows:
+ - 19
+ xorg: 27
+ width: 1
- kind: letter
name: Cl5
+ scancode:
+ keyman: K_T
+ macos: 17
+ windows:
+ - 20
+ xorg: 28
+ width: 1
- - kind: letter
name: Cr7
+ scancode:
+ keyman: K_Z
+ macos: 16
+ windows:
+ - 21
+ xorg: 29
+ width: 1
- kind: letter
name: Cr6
+ scancode:
+ keyman: K_U
+ macos: 32
+ windows:
+ - 22
+ xorg: 30
+ width: 1
- kind: letter
name: Cr5
+ scancode:
+ keyman: K_I
+ macos: 34
+ windows:
+ - 23
+ xorg: 31
+ width: 1
- kind: letter
name: Cr4
+ scancode:
+ keyman: K_O
+ macos: 31
+ windows:
+ - 24
+ xorg: 32
+ width: 1
- kind: letter
name: Cr3
+ scancode:
+ keyman: K_P
+ macos: 35
+ windows:
+ - 25
+ xorg: 33
+ width: 1
- kind: letter
name: Cr2
+ scancode:
+ keyman: K_COLON
+ macos: 33
+ windows:
+ - 26
+ xorg: 34
+ width: 1
- kind: letter
name: Cr1
+ scancode:
+ keyman: K_EQUAL
+ macos: 30
+ windows:
+ - 27
+ xorg: 35
+ width: 1
- kind: multi
name: CD_ret
+ scancode:
+ keyman: K_ENTER
+ macos: 36
+ windows:
+ - 28
+ xorg: 36
span: 2
+ width: 1
- - - name: Dl_caps
+ scancode:
+ keyman: CAPS
+ macos: 57
+ windows:
+ - 58
+ xorg: 66
width: 2
- kind: letter
name: Dl1
+ scancode:
+ keyman: K_A
+ macos: 0
+ windows:
+ - 30
+ xorg: 38
+ width: 1
- kind: letter
name: Dl2
+ scancode:
+ keyman: K_S
+ macos: 1
+ windows:
+ - 31
+ xorg: 39
+ width: 1
- kind: letter
name: Dl3
- - kind: letter
- isMarked: true
+ scancode:
+ keyman: K_D
+ macos: 2
+ windows:
+ - 32
+ xorg: 40
+ width: 1
+ - isMarked: true
+ kind: letter
name: Dl4
+ scancode:
+ keyman: K_F
+ macos: 3
+ windows:
+ - 33
+ xorg: 41
+ width: 1
- kind: letter
name: Dl5
+ scancode:
+ keyman: K_G
+ macos: 5
+ windows:
+ - 34
+ xorg: 42
+ width: 1
- - kind: letter
name: Dr7
- - kind: letter
- isMarked: true
+ scancode:
+ keyman: K_H
+ macos: 4
+ windows:
+ - 35
+ xorg: 43
+ width: 1
+ - isMarked: true
+ kind: letter
name: Dr6
+ scancode:
+ keyman: K_J
+ macos: 38
+ windows:
+ - 36
+ xorg: 44
+ width: 1
- kind: letter
name: Dr5
+ scancode:
+ keyman: K_K
+ macos: 40
+ windows:
+ - 37
+ xorg: 45
+ width: 1
- kind: letter
name: Dr4
+ scancode:
+ keyman: K_L
+ macos: 37
+ windows:
+ - 38
+ xorg: 46
+ width: 1
- kind: letter
name: Dr3
+ scancode:
+ keyman: K_BKQUOTE
+ macos: 41
+ windows:
+ - 39
+ xorg: 47
+ width: 1
- kind: letter
name: Dr2
+ scancode:
+ keyman: K_QUOTE
+ macos: 39
+ windows:
+ - 40
+ xorg: 48
+ width: 1
- kind: letter
name: Dr1
+ scancode:
+ keyman: K_SLASH
+ windows:
+ - 43
+ xorg: 51
+ width: 1
- - - name: El_shift
+ scancode:
+ keyman: SHIFT
+ macos: 57
+ windows:
+ - 42
+ xorg: 50
width: 1.5
- kind: letter
name: El1
+ scancode:
+ keyman: K_oE2
+ windows:
+ - 86
+ xorg: 94
+ width: 1
- kind: letter
name: El2
+ scancode:
+ keyman: K_Y
+ macos: 6
+ windows:
+ - 44
+ xorg: 52
+ width: 1
- kind: letter
name: El3
+ scancode:
+ keyman: K_X
+ macos: 7
+ windows:
+ - 45
+ xorg: 53
+ width: 1
- kind: letter
name: El4
+ scancode:
+ keyman: K_C
+ macos: 8
+ windows:
+ - 46
+ xorg: 54
+ width: 1
- kind: letter
name: El5
+ scancode:
+ keyman: K_V
+ macos: 9
+ windows:
+ - 47
+ xorg: 55
+ width: 1
- kind: letter
name: El6
+ scancode:
+ keyman: K_B
+ macos: 11
+ windows:
+ - 48
+ xorg: 56
+ width: 1
- - kind: letter
name: Er5
+ scancode:
+ keyman: K_N
+ macos: 45
+ windows:
+ - 49
+ xorg: 57
+ width: 1
- kind: letter
name: Er4
+ scancode:
+ keyman: K_M
+ macos: 46
+ windows:
+ - 50
+ xorg: 58
+ width: 1
- kind: letter
name: Er3
+ scancode:
+ keyman: K_COMMA
+ macos: 43
+ windows:
+ - 51
+ xorg: 59
+ width: 1
- kind: letter
name: Er2
+ scancode:
+ keyman: K_PERIOD
+ macos: 47
+ windows:
+ - 52
+ xorg: 60
+ width: 1
- kind: letter
name: Er1
+ scancode:
+ keyman: K_HYPHEN
+ macos: 44
+ windows:
+ - 53
+ xorg: 61
+ width: 1
- name: Er_shift
+ scancode:
+ keyman: SHIFT
+ macos: 60
+ windows:
+ - 54
+ xorg: 62
width: 2.35
- - - name: Fl_ctrl
+ scancode:
+ keyman: LCTRL
+ macos: 59
+ windows:
+ - 29
+ xorg: 37
width: 1.75
- name: Fl_win
+ scancode:
+ keyman: K_?5B
+ macos: 55
+ windows:
+ - 224
+ - 91
+ xorg: 133
width: 1.25
- name: Fl_alt
+ scancode:
+ keyman: LALT
+ macos: 58
+ windows:
+ - 56
+ xorg: 64
width: 1.25
- name: Fl_space
+ scancode:
+ keyman: K_SPACE
+ macos: 49
+ windows:
+ - 57
+ xorg: 65
width: 3
- - name: Fr_space
+ scancode:
+ keyman: K_SPACE
+ macos: 49
+ windows:
+ - 57
+ xorg: 65
width: 3
- name: Fr_altgr
+ scancode:
+ keyman: RALT
+ macos: 61
+ windows:
+ - 224
+ - 56
+ xorg: 108
width: 1.25
- name: Fr_win
+ scancode:
+ keyman: K_?5C
+ macos: 55
+ windows:
+ - 224
+ - 92
+ xorg: 105
width: 1.25
- name: Fr_menu
+ scancode:
+ keyman: K_?5D
+ windows:
+ - 224
+ - 93
+ xorg: 135
width: 1.25
- name: Fr_ctrl
+ scancode:
+ keyman: RCTRL
+ windows:
+ - 224
+ - 29
+ xorg: 105
width: 1.25
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)
diff --git a/lulua/render.py b/lulua/render.py
index 41a6bd5..93d197e 100644
--- a/lulua/render.py
+++ b/lulua/render.py
@@ -302,6 +302,8 @@ def renderXmodmap (args):
keyboard = defaultKeyboards[args.keyboard]
layout = defaultLayouts[args.layout].specialize (keyboard)
+ xorgGetter = lambda x: x.scancode['xorg']
+
with open (args.output, 'w') as fd:
# inspired by https://neo-layout.org/neo_de.xmodmap
fd.write ('\n'.join ([
@@ -321,12 +323,12 @@ def renderXmodmap (args):
# layers: 1, 2, 3, 5, 4, None, 6, 7
for i in (0, 1, 2, 4, 3, 99999, 5, 6):
if i >= len (layout.layers):
- for btn in unique (keyboard.keys (), key=attrgetter ('xorgKeycode')):
+ for btn in unique (keyboard.keys (), key=xorgGetter):
keycodeMap[btn].append ('NoSymbol')
continue
l = layout.layers[i]
# space button shares the same keycode and must be removed
- for btn in unique (keyboard.keys (), key=attrgetter ('xorgKeycode')):
+ for btn in unique (keyboard.keys (), key=xorgGetter):
if not layout.isModifier (frozenset ([btn])):
text = l.layout.get (btn)
if not text:
@@ -359,7 +361,7 @@ def renderXmodmap (args):
for btn, v in keycodeMap.items ():
v = '\t'.join (v)
- fd.write (f'!! {btn.name}\nkeycode {btn.xorgKeycode} = {v}\n')
+ fd.write (f'!! {btn.name}\nkeycode {xorgGetter (btn)} = {v}\n')
fd.write ('\n'.join (['add Mod3 = ISO_First_Group', 'add Mod5 = ISO_Level3_Shift', '']))
def renderKeyman (args):
@@ -388,11 +390,12 @@ def renderKeyman (args):
for i, l in enumerate (layout.layers):
for m in l.modifier:
for x in m:
- if x.keymanCode.startswith ('K_') or x.keymanCode == 'CAPS':
+ keymanCode = x.scancode['keyman']
+ if keymanCode.startswith ('K_') or keymanCode == 'CAPS':
logging.error (f'Keyman does not support custom modifier like {m}. Your layout will not work correctly.')
break
for btn, text in l.layout.items ():
- comb = ' '.join ([x.keymanCode for x in m] + [btn.keymanCode])
+ comb = ' '.join ([x.scancode['keyman'] for x in m] + [btn.scancode['keyman']])
text = ' '.join ([f'U+{ord (x):04X}' for x in text])
fd.write (f'+ [{comb}] > {text}\n')
@@ -494,7 +497,7 @@ def renderWinKbd (args):
s = '\r'
return s
wcharMap = []
- for btn in unique (keyboard.keys (), key=attrgetter ('windowsScancode')):
+ for btn in unique (keyboard.keys (), key=lambda x: x.scancode['windows']):
text = list (layout.getButtonText (btn))
# skip unused keys
@@ -502,7 +505,7 @@ def renderWinKbd (args):
continue
mappedText = [toWindows (s) for s in text]
- vk = next (filter (lambda x: isinstance (x, VirtualKey), scancodeToVk[btn.windowsScancode]))
+ vk = next (filter (lambda x: isinstance (x, VirtualKey), scancodeToVk[btn.scancode['windows']]))
wcharMap.append ((vk, 0, mappedText))
fd.write (makeDriverSources (scancodeToVk, wcharMap))
@@ -539,7 +542,7 @@ def renderKeylayout (args):
for i, l in enumerate (layout.layers):
keymap = ET.SubElement (keymapSet, 'keyMap', index=str (i))
for btn, text in l.layout.items ():
- ET.SubElement (keymap, 'key', code=str (btn.osxKeycode), output=text)
+ ET.SubElement (keymap, 'key', code=str (btn.scancode['macos']), output=text)
layouts = ET.SubElement (docroot, 'layouts')
layout = ET.SubElement (layouts, 'layout', first='0', last='0', modifiers=str (modmapId), mapSet=str (keymapSetId))
diff --git a/lulua/test_keyboard.py b/lulua/test_keyboard.py
index 7537266..d08f6d1 100644
--- a/lulua/test_keyboard.py
+++ b/lulua/test_keyboard.py
@@ -20,7 +20,8 @@
import pytest
-from .keyboard import defaultKeyboards, Button
+from .keyboard import defaultKeyboards, Button, dataDirectory
+from .util import YamlLoader
def test_defaults ():
k = defaultKeyboards['ibmpc105']
@@ -54,6 +55,9 @@ def test_keyboard_getattr ():
assert k['CD_ret'] == k.find ('CD_ret')
assert k['Cr1'] != k.find ('El1')
+ with pytest.raises (KeyError):
+ k['nonexistent_button']
+
def test_button_uniqname ():
a = Button ('a')
assert a.name == 'a'
@@ -77,3 +81,15 @@ def test_button_uniqname ():
d[b] = 2
assert b in d
+ # make sure we can only compare to Buttons
+ assert a != 'hello'
+ assert a != 1
+ assert a != dict ()
+
+def test_serialize ():
+ """ Make sure serialize (deserialize (x)) of keyboards is identity """
+
+ rawKeyboards = YamlLoader (dataDirectory, lambda x: x)
+ name = 'ibmpc105'
+ assert defaultKeyboards[name].serialize () == rawKeyboards[name]
+