From f15b59b9319d4e5a43a3e3515cb0f0449c7224c7 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Sun, 19 Jan 2020 10:11:37 +0100 Subject: Add Windows driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lulua/render.py | 257 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 4 deletions(-) (limited to 'lulua/render.py') 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) -- cgit v1.2.3