summaryrefslogtreecommitdiff
path: root/.gitlab-ci/generate-evdev-keysyms.py
diff options
context:
space:
mode:
Diffstat (limited to '.gitlab-ci/generate-evdev-keysyms.py')
-rwxr-xr-x.gitlab-ci/generate-evdev-keysyms.py161
1 files changed, 161 insertions, 0 deletions
diff --git a/.gitlab-ci/generate-evdev-keysyms.py b/.gitlab-ci/generate-evdev-keysyms.py
new file mode 100755
index 0000000..9c8611b
--- /dev/null
+++ b/.gitlab-ci/generate-evdev-keysyms.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+#
+# Usage: generate-evdev-keysyms.py symbols/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<keycode>\w+)\>\s+{\s+\[(?P<keysyms>[^]]+)\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<keyname>\w+)\s+_EVDEVK\(0x(?P<keycode>[0-9A-Fa-f]+)\).*(?P<kernelname>KEY_\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 <I$keycode> { [ XF86Foo ] }; // KEY_FOO
+ // key <I$keycode> { 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 <I{xkeycode}> {{ [ {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="symbols/inet.in",
+ help="The template file, usually symbols/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()