#!/usr/bin/env python3 # # Usage: generate-evdev-keysyms.py .gitlab-ci/inet.in > symbols/inet # # Generate the symbols/inet file from the names defined in # linux/input-event-codes.h # # Note that this script relies on libevdev to provide the key names and # those are compiled in. Ensure you have a recent-enough libevdev to # generate this list. # import contextlib import argparse import re import sys # The marker to search for in the template file, replaced with our generated # codes. replacement_marker = "@evdevsyms@" # These markers are put into the result file and are used to detect # the section that we added when parsing an existing file. section_header = "Key symbol mappings below are autogenerated" section_footer = "End of autogenerated key symbol mappings" def existing_keysyms(template): """ Return an iterator of type (keycode, [keysym, keysym]) with the strings representing the internal keycode (e.g. I123) and the assigned keysyms. Only the "evdev" xkb_symbols section is parsed. """ start_pattern = re.compile(r"xkb_symbols \"evdev\" {") end_pattern = re.compile(r"^};") # This does not handle multiline keysyms keysym_pattern = re.compile( r"\s+key \<(?P\w+)\>\s+{\s+\[(?P[^]]+)\s*\]\s*};" ) in_evdev = False template.seek(0) for line in template.readlines(): if re.match(start_pattern, line): in_evdev = True continue if in_evdev: if re.match(end_pattern, line): break match = re.match(keysym_pattern, line) if match: keycode = match.group("keycode") keysyms = [k.strip() for k in match.group("keysyms").split(",")] yield keycode, keysyms def xorgproto_evdev_keysyms(header): """ Return an iterator of type ('XF86Foo', xkeycode, kernelname) for the keysyms in the given header file (usually XF86keysym.h). The returned xkeycode is the X keycode value (i.e. kernel value + 8) """ # The two allowed formats in the proto header are: # #define XF86XK_FooBar _EVDEVK(0x123) ... KEY_FOOBAR # /* Use: XF86XK_FooBar _EVDEVK(0x123) ... KEY_FOOBAR # The keysym may not start with XF86XK_ (e.g. "dollar" or "euro") pattern = re.compile( r"(#define|/\* Use:)\s(?P\w+)\s+_EVDEVK\(0x(?P[0-9A-Fa-f]+)\).*(?PKEY_\w+)" ) for line in header: match = re.match(pattern, line) if match: kernelname = match.group("kernelname") keyname = match.group("keyname").replace("XK_", "") keycode = int(match.group("keycode"), 16) xkeycode = keycode + 8 yield keyname, xkeycode, kernelname def generate_symbols_file(template, header): """ Generate a new symbols/inet file with line containing @evdevsyms@ replaced by the full list of evdev keysym mappings, including our section_header/footer. Expected output: :: // $section_header key { [ XF86Foo ] }; // KEY_FOO // key { if assignment already exists }; // KEY_FOO ... // $section_footer Assignments that already exist in the file are commented out. """ # We only care about the I123-style keysyms here keysyms = { int(kc[1:]) for kc, _ in existing_keysyms(template) if re.match(r"I[0-9]{3}", kc) } output = [] template.seek(0) for line in template.readlines(): if replacement_marker not in line: output.append(line) continue output.append(f" // {section_header}\n") for name, xkeycode, kernelname in xorgproto_evdev_keysyms(header): if xkeycode in keysyms: prefix = "//" else: prefix = " " output.append( f"{prefix} key {{ [ {name:30s} ] }}; // {kernelname}\n" ) output.append(f" // {section_footer}\n") return output def main(): parser = argparse.ArgumentParser(description="Generate the evdev symbol lists.") parser.add_argument( "--template", type=argparse.FileType("r"), default=".gitlab-ci/inet.in", help="The template file, usually .gitlab-ci/inet.in", ) parser.add_argument( "--header", type=argparse.FileType("r"), help="Path to the XF86keysym.h header file", default="/usr/include/X11/XF86keysym.h", ) parser.add_argument( "--output", type=str, help="The file to be written to, usually symbols/inet", default="symbols/inet", ) ns = parser.parse_args() with contextlib.ExitStack() as stack: if ns.output == "-": fd = sys.stdout else: fd = stack.enter_context(open(ns.output, "w")) output = generate_symbols_file(ns.template, ns.header) fd.write("".join(output)) if __name__ == "__main__": main()