summaryrefslogtreecommitdiff
path: root/.gitlab-ci
diff options
context:
space:
mode:
Diffstat (limited to '.gitlab-ci')
-rwxr-xr-x.gitlab-ci/generate-evdev-keycodes.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/.gitlab-ci/generate-evdev-keycodes.py b/.gitlab-ci/generate-evdev-keycodes.py
new file mode 100755
index 0000000..5ee1846
--- /dev/null
+++ b/.gitlab-ci/generate-evdev-keycodes.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+#
+# Usage: generate-evdev-keycodes.py keycodes/evdev.in > keycodes/evdev
+#
+# Generate the keycodes/evdev 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 argparse
+import contextlib
+import re
+import sys
+try:
+ import libevdev
+except ImportError:
+ print('WARNING: python-libevdev not available, cannot check for new evdev keycodes', file=sys.stderr)
+ sys.exit(77)
+
+
+# The marker to search for in the template file, replaced with our generated
+# codes.
+replacement_marker = '@evdevkeys@'
+
+# 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 codes below are autogenerated'
+section_footer = 'End of autogenerated key codes'
+
+
+def evdev_codes():
+ '''
+ Return the dict {code, name} for all known evdev codes.
+
+ The list of names is compiled into libevdev.so, use a recent libevdev
+ release to get the most up-to-date list.
+ '''
+ codes = {}
+ for c in libevdev.EV_KEY.codes:
+ # 112 because that's where our 1:1 keycode entries historically
+ # started.
+ # Undefined keys are those with a code < KEY_MAX but without a
+ # #define in the kernel header file
+ if c.value < 112 or not c.is_defined:
+ continue
+
+ if c.name.startswith('BTN_') or c.name == 'KEY_MAX':
+ continue
+
+ codes[c.value] = c.name
+
+ return codes
+
+
+def generate_keycodes_file(template, codes):
+ '''
+ Generate a new keycodes/evdev file with line containing @evdevkeys@
+ replaced by the full list of known evdev key codes, including our
+ section_header/footer. Expected output:
+
+ ::
+
+ // $section_header
+ <I$keycode> = <$keycode + 8> // #define $kernel_name
+ ...
+ // $section_footer
+
+ '''
+ output = []
+ for line in template.readlines():
+ if replacement_marker not in line:
+ output.append(line)
+ continue
+
+ output.append(f'\t// {section_header}\n')
+
+ warned = False
+ for code, name in codes.items():
+ xkeycode = code + 8
+
+ if xkeycode > 255 and not warned:
+ warned = True
+ output.append('\n')
+ output.append('\t// Key codes below cannot be used in X\n')
+ output.append('\n')
+
+ # Special keys that need a comment
+ special_keys = {
+ 211: 'conflicts with AB11',
+ }
+
+ comment = special_keys.get(xkeycode, '')
+ if comment:
+ comment = f' {comment}'
+
+ output.append(f'\t<I{xkeycode}> = {xkeycode};\t// #define {name:23s} {code}{comment}\n')
+ output.append(f'\t// {section_footer}\n')
+
+ return output
+
+
+def extract_generated_keycodes(oldfile):
+ in_generated_section = False
+ pattern = re.compile('.*<I([0-9]*)>.*')
+
+ for line in oldfile:
+ if section_header in line:
+ in_generated_section = True
+ continue
+ elif section_footer in line:
+ return
+ elif in_generated_section:
+ match = pattern.match(line)
+ if match:
+ yield int(match[1])
+
+
+def compare_with(codes, oldfile):
+ '''
+ Extract the <I123> keycodes from between the section_header/footer of
+ oldfile and return a list of keycodes that are in codes but not in
+ oldfile.
+ '''
+ old_keycodes = extract_generated_keycodes(oldfile)
+ keycodes = [c + 8 for c in codes] # X keycode offset
+
+ # This does not detect keycodes in old_keycode but not in the new
+ # generated list - should never happen anyway.
+ return sorted(set(keycodes).difference(old_keycodes))
+
+
+def log_msg(msg):
+ print(msg, file=sys.stderr)
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Generate the evdev keycode lists.')
+ parser.add_argument('--template', type=argparse.FileType('r'),
+ default=open('keycodes/evdev.in'),
+ help='The template file, usually keycodes/evdev.in')
+ parser.add_argument('--output', type=str, default=None, required=True,
+ help='The file to be written to')
+ parser.add_argument('--compare-with', type=argparse.FileType('r'),
+ help='Compare generated output with the given file')
+ parser.add_argument('--verbose', action=argparse.BooleanOptionalAction,
+ help='Print verbose output to stderr')
+ ns = parser.parse_args()
+
+ codes = evdev_codes()
+ rc = 0
+ if ns.verbose:
+ kmin, kmax = min(codes.keys()), max(codes.keys())
+ log_msg(f'evdev keycode range: {kmin} ({kmin:#x}) → {kmax} ({kmax:#x})')
+
+ # We compare before writing so we can use the same filename for
+ # --compare-with and --output. That's also why --output has to be type
+ # str instead of FileType('w').
+ if ns.compare_with:
+ diff = compare_with(codes, ns.compare_with)
+ if diff:
+ rc = 1
+ if ns.verbose:
+ log_msg(f'File {ns.compare_with.name} is out of date, missing keycodes:')
+ for k in diff:
+ name = codes[k - 8] # remove the X offset
+ log_msg(f' <I{k}> // #define {name}')
+
+ with contextlib.ExitStack() as stack:
+ if ns.output == '-':
+ fd = sys.stdout
+ else:
+ fd = stack.enter_context(open(ns.output, 'w'))
+ output = generate_keycodes_file(ns.template, codes)
+ fd.write(''.join(output))
+
+ sys.exit(rc)
+
+
+if __name__ == '__main__':
+ main()