summaryrefslogtreecommitdiff
path: root/lulua/render.py
diff options
context:
space:
mode:
authorLars-Dominik Braun <lars@6xq.net>2020-01-19 10:11:37 +0100
committerLars-Dominik Braun <lars@6xq.net>2020-01-19 10:19:21 +0100
commitf15b59b9319d4e5a43a3e3515cb0f0449c7224c7 (patch)
tree8afadd7358b5e16965e7ab4b3957db702c17869b /lulua/render.py
parent810c8ff0bea17214b4e4c5ce802ad89b5ad6e2c9 (diff)
downloadlulua-f15b59b9319d4e5a43a3e3515cb0f0449c7224c7.tar.gz
lulua-f15b59b9319d4e5a43a3e3515cb0f0449c7224c7.tar.bz2
lulua-f15b59b9319d4e5a43a3e3515cb0f0449c7224c7.zip
Add Windows driver
Generate C header file based on layout description and create a source bundle that must be compiled on a Windows system and then moved back to the source tree. This sucks, but cross-compiling on Linux is a pain, since Windows’ development headers assume a case-insensitive filesystem. Also I’m using MSKLC because the latest driver development kit cannot compile these drivers correctly. Dear @microsoft, please fix your shit: https://github.com/microsoft/Windows-driver-samples/issues/433 A remaining concern right now is licensing. keyboard.{c,h,def,rc} have been copied from a project generated by MSKLC and are probably non-free, although pretty much identical files like https://github.com/microsoft/Windows-driver-samples/blob/master/input/layout/kbdus/kbdus.c are covered by MS-PL. Also binds backspace key to \b and adjusts xmodmap/svg rendering accordingly. See #7.
Diffstat (limited to 'lulua/render.py')
-rw-r--r--lulua/render.py257
1 files changed, 253 insertions, 4 deletions
diff --git a/lulua/render.py b/lulua/render.py
index be3bf68..3bde0b7 100644
--- a/lulua/render.py
+++ b/lulua/render.py
@@ -290,10 +290,7 @@ def renderXmodmap (args):
if not layout.isModifier (frozenset ([btn])):
text = l.layout.get (btn)
if not text:
- if btn.name == 'Br_bs' and i == 0:
- text = 'BackSpace'
- else:
- text = 'NoSymbol'
+ text = 'NoSymbol'
else:
# some keys cannot be represented by unicode
# characters and must be mapped
@@ -301,6 +298,7 @@ def renderXmodmap (args):
'\t': 'Tab',
'\n': 'Return',
' ': 'space',
+ '\b': 'BackSpace',
}
text = specialMap.get (text, f'U{ord (text):04X}')
keycodeMap[btn].append (text)
@@ -426,6 +424,255 @@ def renderAsk (args):
tree = ET.ElementTree (kbdelem)
tree.write (args.output, encoding='utf-8', xml_declaration=True)
+def renderWinKbd (args):
+ keyboard = defaultKeyboards[args.keyboard]
+ layout = defaultLayouts[args.layout].specialize (keyboard)
+
+ if args.layout != 'ar-lulua':
+ logging.error (f'due to the delicate relationship between virtual keys and text output this command will probably not produce working files for your layout. Please have a look at renderWinKbd() in {__file__} and fix the code.')
+ return
+
+ resPath = pkg_resources.resource_filename (__package__, 'data/winkbd')
+
+ with open (args.output, 'w') as fd:
+ lines = []
+ lines.append (f'/* This header was auto-generated by {__package__}. It is included by {resPath}/keyboard.h. Do not modify. */')
+ lines.append ('')
+
+ # copied from kbdneo2.c as well
+ # XXX: modifier keys are fixed for now
+ # maps virtual keys (first value) to shift bitfield value (second value)
+ lines.append ('#define MODIFIER_BITS\\')
+ lines.append (""" { VK_SHIFT , KBDSHIFT }, \\
+ { VK_CONTROL , KBDCTRL }, \\
+ { VK_MENU , KBDALT }, \\
+ { VK_OEM_8 , KBDKANA }, \\
+ { VK_OEM_102 , 16 },""")
+
+ # copied from kbdneo2.c
+ # maps a shift bitfield value (array index) to a layer number in
+ # virtual key translation (VK_TO_WCHARS, array value)
+ lines.append ('#define CHAR_MODIFIERS_MASK 24')
+ lines.append ('#define CHAR_MODIFIERS\\')
+ lines.append ('\t{ 0, 1, 6, 7, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 3, 8, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 2, 4, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, SHFT_INVALID, 5, }')
+
+ # this is the standard layout from windows’ kbd.h (for KBD_TYPE == 4)
+ scancodeToVk = {
+ 'T01': 'ESCAPE',
+ 'T02': '1',
+ 'T03': '2',
+ 'T04': '3',
+ 'T05': '4',
+ 'T06': '5',
+ 'T07': '6',
+ 'T08': '7',
+ 'T09': '8',
+ 'T0A': '9',
+ 'T0B': '0',
+ 'T0C': 'OEM_MINUS',
+ 'T0D': 'OEM_PLUS',
+ 'T0E': 'BACK',
+ 'T0F': 'TAB',
+ 'T10': 'Q',
+ 'T11': 'W',
+ 'T12': 'E',
+ 'T13': 'R',
+ 'T14': 'T',
+ 'T15': 'Y',
+ 'T16': 'U',
+ 'T17': 'I',
+ 'T18': 'O',
+ 'T19': 'P',
+ 'T1A': 'OEM_4',
+ 'T1B': 'OEM_6',
+ 'T1C': 'RETURN',
+ 'T1D': 'LCONTROL',
+ 'T1E': 'A',
+ 'T1F': 'S',
+ 'T20': 'D',
+ 'T21': 'F',
+ 'T22': 'G',
+ 'T23': 'H',
+ 'T24': 'J',
+ 'T25': 'K',
+ 'T26': 'L',
+ 'T27': 'OEM_1',
+ 'T28': 'OEM_7',
+ 'T29': 'OEM_3',
+ 'T2A': 'LSHIFT',
+ 'T2B': 'OEM_5',
+ 'T2C': 'Z',
+ 'T2D': 'X',
+ 'T2E': 'C',
+ 'T2F': 'V',
+ 'T30': 'B',
+ 'T31': 'N',
+ 'T32': 'M',
+ 'T33': 'OEM_COMMA',
+ 'T34': 'OEM_PERIOD',
+ 'T35': 'OEM_2',
+ 'T36': 'RSHIFT',
+ 'T37': 'MULTIPLY',
+ 'T38': 'LMENU',
+ 'T39': 'SPACE',
+ 'T3A': 'CAPITAL',
+ 'T3B': 'F1',
+ 'T3C': 'F2',
+ 'T3D': 'F3',
+ 'T3E': 'F4',
+ 'T3F': 'F5',
+ 'T40': 'F6',
+ 'T41': 'F7',
+ 'T42': 'F8',
+ 'T43': 'F9',
+ 'T44': 'F10',
+ 'T45': 'NUMLOCK',
+ 'T46': 'SCROLL',
+ 'T47': 'HOME',
+ 'T48': 'UP',
+ 'T49': 'PRIOR',
+ 'T4A': 'SUBTRACT',
+ 'T4B': 'LEFT',
+ 'T4C': 'CLEAR',
+ 'T4D': 'RIGHT',
+ 'T4E': 'ADD',
+ 'T4F': 'END',
+ 'T50': 'DOWN',
+ 'T51': 'NEXT',
+ 'T52': 'INSERT',
+ 'T53': 'DELETE',
+ 'T54': 'SNAPSHOT',
+ 'T56': 'OEM_102',
+ 'T57': 'F11',
+ 'T58': 'F12',
+ 'T59': 'CLEAR',
+ 'T5A': 'OEM_WSCTRL',
+ 'T5B': 'OEM_FINISH',
+ 'T5C': 'OEM_JUMP',
+ 'T5D': 'EREOF',
+ 'T5E': 'OEM_BACKTAB',
+ 'T5F': 'OEM_AUTO',
+ 'T62': 'ZOOM',
+ 'T63': 'HELP',
+ 'T64': 'F13',
+ 'T65': 'F14',
+ 'T66': 'F15',
+ 'T67': 'F16',
+ 'T68': 'F17',
+ 'T69': 'F18',
+ 'T6A': 'F19',
+ 'T6B': 'F20',
+ 'T6C': 'F21',
+ 'T6D': 'F22',
+ 'T6E': 'F23',
+ 'T6F': 'OEM_PA3',
+ 'T71': 'OEM_RESET',
+ 'T73': 'ABNT_C1',
+ 'T76': 'F24',
+ 'T7B': 'OEM_PA1',
+ 'T7C': 'TAB',
+ 'T7E': 'ABNT_C2',
+ 'T7F': 'OEM_PA2',
+
+ 'X10': 'MEDIA_PREV_TRACK',
+ 'X19': 'MEDIA_NEXT_TRACK',
+ 'X1C': 'RETURN',
+ 'X1D': 'RCONTROL',
+ 'X20': 'VOLUME_MUTE',
+ 'X21': 'LAUNCH_APP2',
+ 'X22': 'MEDIA_PLAY_PAUSE',
+ 'X24': 'MEDIA_STOP',
+ 'X2E': 'VOLUME_DOWN',
+ 'X30': 'VOLUME_UP',
+ 'X32': 'BROWSER_HOME',
+ 'X35': 'DIVIDE',
+ 'X37': 'SNAPSHOT',
+ 'X38': 'RMENU',
+ 'X46': 'CANCEL',
+ 'X47': 'HOME',
+ 'X48': 'UP',
+ 'X49': 'PRIOR',
+ 'X4B': 'LEFT',
+ 'X4D': 'RIGHT',
+ 'X4F': 'END',
+ 'X50': 'DOWN',
+ 'X51': 'NEXT',
+ 'X52': 'INSERT',
+ 'X53': 'DELETE',
+ 'X5B': 'LWIN',
+ 'X5C': 'RWIN',
+ 'X5D': 'APPS',
+ 'X5E': 'POWER',
+ 'X5F': 'SLEEP',
+ 'X65': 'BROWSER_SEARCH',
+ 'X66': 'BROWSER_FAVORITES',
+ 'X67': 'BROWSER_REFRESH',
+ 'X68': 'BROWSER_STOP',
+ 'X69': 'BROWSER_FORWARD',
+ 'X6A': 'BROWSER_BACK',
+ 'X6B': 'LAUNCH_APP1',
+ 'X6C': 'LAUNCH_MAIL',
+ 'X6D': 'LAUNCH_MEDIA_SELECT',
+ }
+ # modifications copied from kbdneo2.c
+ # maps modifier keys to oem values used above.
+ scancodeToVk.update ({
+ # mod 3
+ 'T2B': 'OEM_102',
+ 'T3A': 'OEM_102',
+ # mod 4
+ 'X38': 'OEM_8',
+ 'T56': 'OEM_8',
+ })
+ for k, v in scancodeToVk.items ():
+ lines.append (f'#undef {k}')
+ if len (v) == 1:
+ # character value if not symbolic
+ v = f"'{v}'"
+ lines.append (f'#define {k} _EQ({v})')
+ lines.append ('')
+
+ lines.append ('#define VK_TO_WCH6 \\')
+ for btn in unique (keyboard.keys (), key=attrgetter ('windowsScancode')):
+ def toUnicode (s):
+ if s is None:
+ return 'WCH_NONE'
+ elif len (s) != 1:
+ logging.error (f'only single-character strings are supported, ignoring {s}')
+ return 'WCH_NONE'
+ elif s == '\n':
+ # convert to windows-convention
+ s = '\r'
+ return f'0x{ord (s):x} /*{repr (s)}*/'
+
+ text = list (layout.getButtonText (btn))
+ assert len (text) < 7, "supporting six layers only right now"
+
+ # skip unused keys
+ if sum (map (lambda x: 1 if x is not None else 0, text)) == 0:
+ continue
+
+ # fixed-length array, need padding
+ mappedText = [toUnicode (s) for s in text]
+ while len (mappedText) < 6:
+ mappedText.append ('WCH_NONE')
+
+ vk = scancodeToVk[btn.windowsScancode]
+ if len (vk) == 1:
+ # character value
+ vk = f"'{vk}'"
+ else:
+ # symbolic value
+ vk = f'VK_{vk}'
+ mappingStr = ', '.join (mappedText)
+ lines.append (f'\t{{ {vk}, 0, {mappingStr} }}, \\')
+ # NUL-termination entry is already present in keyboard.c
+ lines.append ('\n')
+
+ fd.write ('\n'.join (lines))
+ logging.info ('refer to README.rst on how to build a windows driver. '
+ f'Template files are located in {resPath}')
+
def yamlload (s):
try:
with open (s) as fd:
@@ -458,6 +705,8 @@ def render ():
sp.set_defaults (func=renderKeyman)
sp = subparsers.add_parser('ask')
sp.set_defaults (func=renderAsk)
+ sp = subparsers.add_parser('winkbd')
+ sp.set_defaults (func=renderWinKbd)
parser.add_argument('output', metavar='FILE', help='Output file')
logging.basicConfig (level=logging.INFO)