diff options
Diffstat (limited to '.gitlab-ci')
-rwxr-xr-x | .gitlab-ci/generate-evdev-keycodes.py | 183 |
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() |