summaryrefslogtreecommitdiff
path: root/.gitlab-ci
diff options
context:
space:
mode:
authorPeter Hutterer <peter.hutterer@who-t.net>2021-01-08 14:33:56 +1000
committerSergey Udaltsov <sergey.udaltsov@gmail.com>2021-01-18 10:58:58 +0000
commit5dc9b48c9b31de9f9780887a79ded3b1e52591d9 (patch)
treed1482f67a6baeb6019d03bc4a58e2a20d1b03552 /.gitlab-ci
parenta9639617582bfc6ce81d9553d92dbea2ae80e061 (diff)
downloadxkeyboard-config-5dc9b48c9b31de9f9780887a79ded3b1e52591d9.tar.gz
gitlab CI: generate the evdev keycodes
The various <I123> keycodes in keycodes/evdev simply match the kernel defines + offset 8. There is no need to maintain these manually, let's generate them instead. Keycodes update rarely and irregularly (on average maybe every second kernel release) so there's no need to integrate this into the build itself, let's add it to our CI instead. The script here uses python-libevdev which has a list of the various key codes and their names (compile-time built-in in libevdev itself so it's advisable that a recent libevdev is used). The script is hooked up to a custom job that will fail if there are key codes with a #define in the kernel that are not listed in our evdev file. We allow that job to fail, it's not that urgent to prevent any other pipelines. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
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()