diff options
author | Daniel P. Berrange <berrange@redhat.com> | 2017-02-15 14:19:28 +0000 |
---|---|---|
committer | Daniel P. Berrange <berrange@redhat.com> | 2017-02-15 14:25:11 +0000 |
commit | 0071933e9f0e614f3b6c94bf5f75b8e0b2d95f08 (patch) | |
tree | 52bf4dbe1aea1e55a9a86dbb6f19aaa452e600a8 /tools | |
download | keycodemapdb-0071933e9f0e614f3b6c94bf5f75b8e0b2d95f08.zip keycodemapdb-0071933e9f0e614f3b6c94bf5f75b8e0b2d95f08.tar.gz keycodemapdb-0071933e9f0e614f3b6c94bf5f75b8e0b2d95f08.tar.bz2 |
Initial import of code
The data/keymaps.csv file is taken from the latest SPICE-GTK
repository. All contributions from this original file in both
SPICE-GTK and GTK-VNC repos were made under Red Hat copyright.
Red Hat grants permission to relicense to dual GPL2 or 3-clause
BSD.
The data/keymap-gen tool is a brand new file.
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
Diffstat (limited to 'tools')
-rwxr-xr-x | tools/keymap-gen | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/tools/keymap-gen b/tools/keymap-gen new file mode 100755 index 0000000..36e20a2 --- /dev/null +++ b/tools/keymap-gen @@ -0,0 +1,559 @@ +#!/usr/bin/python +# -*- python -*- +# +# Keycode Map Generator +# +# Copyright (C) 2009-2017 Red Hat, Inc. +# +# This file is dual license under the terms of the GPLv2 or later +# and 3-clause BSD licenses. +# + +import csv +import argparse +import hashlib +import time + +class Database: + + # Linux: linux/input.h + MAP_LINUX = "linux" + + # OS-X: Carbon/HIToolbox/Events.h + MAP_OSX = "osx" + + # AT Set 1: linux/drivers/input/keyboard/atkbd.c + # (atkbd_set2_keycode + atkbd_unxlate_table) + MAP_ATSET1 = "atset1" + + # AT Set 2: linux/drivers/input/keyboard/atkbd.c + # (atkbd_set2_keycode) + MAP_ATSET2 = "atset2" + + # AT Set 3: linux/drivers/input/keyboard/atkbd.c + # (atkbd_set3_keycode) + MAP_ATSET3 = "atset3" + + # XT: linux/drivers/input/keyboard/xt.c (xtkbd_keycode) + MAP_XT = "xt" + + # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes) + MAP_XTKBD = "xtkbd" + + # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode) + MAP_USB = "usb" + + # Win32: mingw32/winuser.h + MAP_WIN32 = "win32" + + # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} + # (xt + manually transcribed) + MAP_XWINXT = "xwinxt" + + # XKBD XT: xf86-input-keyboard/src/at_scancode.c + # (xt + manually transcribed) + MAP_XKBDXT = "xkbdxt" + + # Xorg with evdev: linux + an offset + MAP_XORGEVDEV = "xorgevdev" + + # Xorg with kbd: xkbdxt + an offset + MAP_XORGKBD = "xorgkbd" + + # Xorg with OS-X: osx + an offset + MAP_XORGXQUARTZ = "xorgxquartz" + + # Xorg + Cygwin: xwinxt + an offset + MAP_XORGXWIN = "xorgxwin" + + # XT over RFB: xtkbd + special re-encoding of high bit + MAP_RFB = "rfb" + + MAP_LIST = ( + MAP_LINUX, + MAP_OSX, + MAP_ATSET1, + MAP_ATSET2, + MAP_ATSET3, + MAP_XT, + MAP_XTKBD, + MAP_USB, + MAP_WIN32, + MAP_XWINXT, + MAP_XKBDXT, + + # These are derived from maps above + MAP_XORGEVDEV, + MAP_XORGKBD, + MAP_XORGXQUARTZ, + MAP_XORGXWIN, + MAP_RFB, + ) + + CODE_COLUMNS = { + MAP_LINUX: 1, + MAP_OSX: 3, + MAP_ATSET1: 4, + MAP_ATSET2: 5, + MAP_ATSET3: 6, + MAP_XT: 7, + MAP_XTKBD: 8, + MAP_USB: 9, + MAP_WIN32: 11, + MAP_XWINXT: 12, + MAP_XKBDXT: 13, + } + + NAME_COLUMNS = { + MAP_LINUX: 0, + MAP_OSX: 2, + MAP_WIN32: 10, + } + + def __init__(self): + + self.mapto = {} + self.mapfrom = {} + self.mapname = {} + self.mapchecksum = None + + for name in self.MAP_LIST: + # Key is a MAP_LINUX, value is a MAP_XXX + self.mapto[name] = {} + # key is a MAP_XXX, value is a MAP_LINUX + self.mapfrom[name] = {} + + for name in self.NAME_COLUMNS.keys(): + # key is a MAP_LINUX, value is a string + self.mapname[name] = {} + + def _generate_checksum(self, filename): + hash = hashlib.sha256() + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash.update(chunk) + self.mapchecksum = hash.hexdigest() + + def load(self, filename): + self._generate_checksum(filename) + + with open(filename, 'rb') as f: + reader = csv.reader(f) + + # Discard column headings + reader.next() + + for row in reader: + # We special case MAP_LINUX since that is out + # master via which all other mappings are done + linux = self.load_linux(row) + + # Now load all the remaining master data values + self.load_data(row, linux) + + # Then load all the keycode names + self.load_names(row, linux) + + # Finally calculate derived key maps + self.derive_data(row, linux) + + def load_linux(self, row): + col = self.CODE_COLUMNS[self.MAP_LINUX] + linux = row[col] + + if linux.startswith("0x"): + linux = int(linux, 16) + else: + linux = int(linux, 10) + + self.mapto[self.MAP_LINUX][linux] = linux + self.mapfrom[self.MAP_LINUX][linux] = linux + + return linux + + + def load_data(self, row, linux): + for mapname in self.CODE_COLUMNS: + if mapname == self.MAP_LINUX: + continue + + col = self.CODE_COLUMNS[mapname] + val = row[col] + + if val == "": + continue + + if val.startswith("0x"): + val = int(val, 16) + else: + val = int(val, 10) + + self.mapto[mapname][linux] = val + self.mapfrom[mapname][val] = linux + + def load_names(self, row, linux): + for mapname in self.NAME_COLUMNS: + col = self.NAME_COLUMNS[mapname] + val = row[col] + + if val == "": + continue + + self.mapname[mapname][linux] = val + + + def derive_data(self, row, linux): + # Xorg KBD is XKBD XT offset by 8 + if linux in self.mapto[self.MAP_XKBDXT]: + xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8 + self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgkbd + self.mapfrom[self.MAP_XORGXQUARTZ][xorgkbd] = linux + + # Xorg evdev is Linux offset by 8 + self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8 + self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux + + # Xorg XQuartx is OS-X offset by 8 + if linux in self.mapto[self.MAP_OSX]: + xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8 + self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz + self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux + + # Xorg Xwin (aka Cygwin) is XWin XT offset by 8 + if linux in self.mapto[self.MAP_XWINXT]: + xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8 + self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin + self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux + + + # RFB keycodes are XT kbd keycodes with a slightly + # different encoding of 0xe0 scan codes. RFB uses + # the high bit of the first byte, instead of the low + # bit of the second byte + if linux in self.mapto[self.MAP_XTKBD]: + xtkbd = self.mapto[self.MAP_XTKBD][linux] + if xtkbd != 0: + rfb = ((xtkbd & 0x100) >> 1) | (xtkbd & 0x7f) + else: + rfb = 0 + self.mapto[self.MAP_RFB][linux] = rfb + self.mapfrom[self.MAP_RFB][rfb] = linux + +class LanguageGenerator(object): + + def generate_header(self, database, args): + today = time.strftime("%Y-%m-%d %H:%M") + self._boilerplate([ + "This file is auto-generated from keymaps.csv on %s" % today, + "Database checksum sha256(%s)" % database.mapchecksum, + "To re-generate, run:", + " %s" % args, + ]) + + def generate_code_map(self, varname, database, frommapname, tomapname): + if frommapname not in database.mapfrom: + raise Exception("Unknown map %s, expected %s", + frommapname, ",".join(database.mapfrom.keys())) + if tomapname not in database.mapto: + raise Exception("Unknown map %s, expected %s", + tomapname, ",".join(database.mapto.keys())) + + tolinux = database.mapfrom[frommapname] + fromlinux = database.mapto[tomapname] + + if varname is None: + varname = "code_map_%s_to_%s" % (frommapname, tomapname) + + frommax = 0 + for key in tolinux.keys(): + if key > frommax: + frommax = key + + self._array_start_code(varname, frommax + 1) + for src in range(frommax + 1): + linux = tolinux.get(src, None) + if linux is None: + dst = None + else: + dst = fromlinux.get(linux, None) + + comment = "%s -> %s -> %s" % (self._label(database, frommapname, src), + self._label(database, Database.MAP_LINUX, linux), + self._label(database, tomapname, dst)) + self._array_entry_code(src, dst, comment) + self._array_end() + + def generate_code_table(self, varname, database, mapname): + if mapname not in database.mapto: + raise Exception("Unknown map %s, expected %s", + mapname, ",".join(database.mapto.keys())) + + keys = database.mapto[Database.MAP_LINUX].keys() + keys.sort() + names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] + + if varname is None: + varname = "code_table_%s" % mapname + + self._array_start_code(varname, len(keys)) + + for i in range(len(keys)): + key = keys[i] + dst = database.mapto[mapname].get(key, None) + self._array_entry_code(i, dst, names[i]) + + self._array_end() + + def generate_name_map(self, varname, database, frommapname, tomapname): + if frommapname not in database.mapfrom: + raise Exception("Unknown map %s, expected %s", + frommapname, ",".join(database.mapfrom.keys())) + if tomapname not in database.mapname: + raise Exception("Unknown map %s, expected %s", + tomapname, ",".join(database.mapname.keys())) + + tolinux = database.mapfrom[frommapname] + fromlinux = database.mapname[tomapname] + + if varname is None: + varname = "name_map_%s_to_%s" % (frommapname, tomapname) + + frommax = 0 + for key in tolinux.keys(): + if key > frommax: + frommax = key + + self._array_start_name(varname, frommax + 1) + + for src in range(frommax + 1): + linux = tolinux.get(src, None) + if linux is None: + dst = None + else: + dst = fromlinux.get(linux, None) + + comment = "%s -> %s -> %s" % (self._label(database, frommapname, src), + self._label(database, Database.MAP_LINUX, linux), + self._label(database, tomapname, dst)) + self._array_entry_name(src, dst, comment) + self._array_end() + + def generate_name_table(self, varname, database, mapname): + if mapname not in database.mapname: + raise Exception("Unknown map %s, expected %s", + mapname, ",".join(database.mapname.keys())) + + keys = database.mapto[Database.MAP_LINUX].keys() + keys.sort() + names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] + + if varname is None: + varname = "name_table_%s" % mapname + + self._array_start_name(varname, len(keys)) + + for i in range(len(keys)): + key = keys[i] + dst = database.mapname[mapname].get(key, None) + self._array_entry_name(i, dst, names[i]) + + self._array_end() + + def _label(self, database, mapname, val): + if mapname in database.mapname: + return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(val, "unnamed")) + else: + return "%s:%s" % (mapname, val) + + +class CLanguageGenerator(LanguageGenerator): + + def __init__(self, inttypename, strtypename): + self.inttypename = inttypename + self.strtypename = strtypename + + def _boilerplate(self, lines): + print "/*" + for line in lines: + print " * %s" % line + print "*/" + + def _array_start_code(self, varname, length): + print "const %s %s[%d] = {" % (self.inttypename, varname, length) + + def _array_start_name(self, varname, length): + print "const %s %s[%d] = {" % (self.strtypename, varname, length) + + def _array_end(self): + print "};" + + def _array_entry_code(self, index, value, comment): + if value is not None: + print " [0x%x] = 0x%x, /* %s */" % (index, value, comment) + + def _array_entry_name(self, index, value, comment): + if value is not None: + print " [0x%x] = \"%s\", /* %s */" % (index, value, comment) + +class StdCLanguageGenerator(CLanguageGenerator): + + def __init__(self): + super(StdCLanguageGenerator, self).__init__("int", "char *") + +class GLib2LanguageGenerator(CLanguageGenerator): + + def __init__(self): + super(GLib2LanguageGenerator, self).__init__("gint32", "gchar *") + +class PythonLanguageGenerator(LanguageGenerator): + + def _boilerplate(self, lines): + print "#" + for line in lines: + print "# %s" % line + print "#" + + def _array_start_code(self, varname, length): + print "%s = [" % varname + + def _array_start_name(self, varname, length): + print "%s = [" % varname + + def _array_end(self): + print "]" + + def _array_entry_code(self, index, value, comment): + if value is None: + print " None, # %s" % (comment) + else: + print " 0x%x, # %s" % (value, comment) + + def _array_entry_name(self, index, value, comment): + if value is None: + print " None, # %s" % (comment) + else: + print " \"%s\", # %s" % (value, comment) + +class PerlLanguageGenerator(LanguageGenerator): + + def _boilerplate(self, lines): + print "#" + for line in lines: + print "# %s" % line + print "#" + + def _array_start_code(self, varname, length): + print "my @%s = (" % varname + + def _array_start_name(self, varname, length): + print "my @%s = (" % varname + + def _array_end(self): + print ");" + + def _array_entry_code(self, index, value, comment): + if value is None: + print " undef, # %s" % (comment) + else: + print " 0x%x, # %s" % (value, comment) + + def _array_entry_name(self, index, value, comment): + if value is None: + print " undef, # %s" % (comment) + else: + print " \"%s\", # %s" % (value, comment) + +GENERATORS = { + "stdc": StdCLanguageGenerator(), + "glib2": StdCLanguageGenerator(), + "python2": PythonLanguageGenerator(), + "python3": PythonLanguageGenerator(), + "perl": PerlLanguageGenerator(), +} + +def code_map(args): + database = Database() + database.load(args.keymaps) + + cliargs = ["keymap-gen", "--lang=%s" % args.lang] + if args.varname is not None: + cliargs.append("--varname=%s" % args.varname) + cliargs.extend(["code-map", "keymaps.csv", args.frommapname, args.tomapname]) + GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) + + GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname) + +def code_table(args): + database = Database() + database.load(args.keymaps) + + cliargs = ["keymap-gen", "--lang=%s" % args.lang] + if args.varname is not None: + cliargs.append("--varname=%s" % args.varname) + cliargs.extend(["code-table", "keymaps.csv", args.mapname]) + GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) + + GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname) + +def name_map(args): + database = Database() + database.load(args.keymaps) + + cliargs = ["keymap-gen", "--lang=%s" % args.lang] + if args.varname is not None: + cliargs.append("--varname=%s" % args.varname) + cliargs.extend(["name-map", "keymaps.csv", args.frommapname, args.tomapname]) + GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) + + GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname) + +def name_table(args): + database = Database() + database.load(args.keymaps) + + + cliargs = ["keymap-gen", "--lang=%s" % args.lang] + if args.varname is not None: + cliargs.append("--varname=%s" % args.varname) + cliargs.extend(["name-table", "keymaps.csv", args.mapname]) + GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) + + GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname) + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument("--lang", default="stdc", + help="Output language, %s" % ",".join(GENERATORS.keys())) + parser.add_argument("--varname", default=None, + help="Data variable name") + + subparsers = parser.add_subparsers(help="sub-command help") + + codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables") + codemapparser.add_argument("keymaps", help="Path to keymap CSV data file") + codemapparser.add_argument("frommapname", help="Source code table name") + codemapparser.add_argument("tomapname", help="Target code table name") + codemapparser.set_defaults(func=code_map) + + codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table") + codetableparser.add_argument("keymaps", help="Path to keymap CSV data file") + codetableparser.add_argument("mapname", help="Code table name") + codetableparser.set_defaults(func=code_table) + + namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names") + namemapparser.add_argument("keymaps", help="Path to keymap CSV data file") + namemapparser.add_argument("frommapname", help="Source code table name") + namemapparser.add_argument("tomapname", help="Target name table name") + namemapparser.set_defaults(func=name_map) + + nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table") + nametableparser.add_argument("keymaps", help="Path to keymap CSV data file") + nametableparser.add_argument("mapname", help="Name table name") + nametableparser.set_defaults(func=name_table) + + args = parser.parse_args() + args.func(args) + + +main() |