Use print_function in python 2
[keycodemapdb.git] / tools / keymap-gen
1 #!/usr/bin/python
2 # -*- python -*-
3 #
4 # Keycode Map Generator
5 #
6 # Copyright (C) 2009-2017 Red Hat, Inc.
7 #
8 # This file is dual license under the terms of the GPLv2 or later
9 # and 3-clause BSD licenses.
10 #
11
12 # Requires >= 2.6
13 from __future__ import print_function
14
15 import csv
16 import argparse
17 import hashlib
18 import time
19
20 class Database:
21
22     # Linux: linux/input.h
23     MAP_LINUX = "linux"
24
25     # OS-X: Carbon/HIToolbox/Events.h
26     MAP_OSX = "osx"
27
28     # AT Set 1: linux/drivers/input/keyboard/atkbd.c
29     #           (atkbd_set2_keycode + atkbd_unxlate_table)
30     MAP_ATSET1 = "atset1"
31
32     # AT Set 2: linux/drivers/input/keyboard/atkbd.c
33     #           (atkbd_set2_keycode)
34     MAP_ATSET2 = "atset2"
35
36     # AT Set 3: linux/drivers/input/keyboard/atkbd.c
37     #           (atkbd_set3_keycode)
38     MAP_ATSET3 = "atset3"
39
40     # XT: linux/drivers/input/keyboard/xt.c (xtkbd_keycode)
41     MAP_XT = "xt"
42
43     # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes)
44     MAP_XTKBD = "xtkbd"
45
46     # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode)
47     MAP_USB = "usb"
48
49     # Win32: mingw32/winuser.h
50     MAP_WIN32 = "win32"
51
52     # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h}
53     #          (xt + manually transcribed)
54     MAP_XWINXT = "xwinxt"
55
56     # X11: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
57     MAP_X11 = "x11"
58
59     # XKBD XT: xf86-input-keyboard/src/at_scancode.c
60     #          (xt + manually transcribed)
61     MAP_XKBDXT = "xkbdxt"
62
63     # Xorg with evdev: linux + an offset
64     MAP_XORGEVDEV = "xorgevdev"
65
66     # Xorg with kbd: xkbdxt + an offset
67     MAP_XORGKBD = "xorgkbd"
68
69     # Xorg with OS-X: osx + an offset
70     MAP_XORGXQUARTZ = "xorgxquartz"
71
72     # Xorg + Cygwin: xwinxt + an offset
73     MAP_XORGXWIN = "xorgxwin"
74
75     # XT over RFB: xtkbd + special re-encoding of high bit
76     MAP_RFB = "rfb"
77
78     MAP_LIST = (
79         MAP_LINUX,
80         MAP_OSX,
81         MAP_ATSET1,
82         MAP_ATSET2,
83         MAP_ATSET3,
84         MAP_XT,
85         MAP_XTKBD,
86         MAP_USB,
87         MAP_WIN32,
88         MAP_XWINXT,
89         MAP_XKBDXT,
90         MAP_X11,
91
92         # These are derived from maps above
93         MAP_XORGEVDEV,
94         MAP_XORGKBD,
95         MAP_XORGXQUARTZ,
96         MAP_XORGXWIN,
97         MAP_RFB,
98     )
99
100     CODE_COLUMNS = {
101         MAP_LINUX: 1,
102         MAP_OSX: 3,
103         MAP_ATSET1: 4,
104         MAP_ATSET2: 5,
105         MAP_ATSET3: 6,
106         MAP_XT: 7,
107         MAP_XTKBD: 8,
108         MAP_USB: 9,
109         MAP_WIN32: 11,
110         MAP_XWINXT: 12,
111         MAP_XKBDXT: 13,
112         MAP_X11: 15,
113     }
114
115     NAME_COLUMNS = {
116         MAP_LINUX: 0,
117         MAP_OSX: 2,
118         MAP_WIN32: 10,
119         MAP_X11: 14,
120     }
121
122     def __init__(self):
123
124         self.mapto = {}
125         self.mapfrom = {}
126         self.mapname = {}
127         self.mapchecksum = None
128
129         for name in self.MAP_LIST:
130             # Key is a MAP_LINUX, value is a MAP_XXX
131             self.mapto[name] = {}
132             # key is a MAP_XXX, value is a MAP_LINUX
133             self.mapfrom[name] = {}
134
135         for name in self.NAME_COLUMNS.keys():
136             # key is a MAP_LINUX, value is a string
137             self.mapname[name] = {}
138
139     def _generate_checksum(self, filename):
140         hash = hashlib.sha256()
141         with open(filename, "rb") as f:
142             for chunk in iter(lambda: f.read(4096), b""):
143                 hash.update(chunk)
144         self.mapchecksum = hash.hexdigest()
145
146     def load(self, filename):
147         self._generate_checksum(filename)
148
149         with open(filename, 'r') as f:
150             reader = csv.reader(f)
151
152             first = True
153
154             for row in reader:
155                 # Discard column headings
156                 if first:
157                     first = False
158                     continue
159
160                 # We special case MAP_LINUX since that is out
161                 # master via which all other mappings are done
162                 linux = self.load_linux(row)
163
164                 # Now load all the remaining master data values
165                 self.load_data(row, linux)
166
167                 # Then load all the keycode names
168                 self.load_names(row, linux)
169
170                 # Finally calculate derived key maps
171                 self.derive_data(row, linux)
172
173     def load_linux(self, row):
174         col = self.CODE_COLUMNS[self.MAP_LINUX]
175         linux = row[col]
176
177         if linux.startswith("0x"):
178             linux = int(linux, 16)
179         else:
180             linux = int(linux, 10)
181
182         self.mapto[self.MAP_LINUX][linux] = linux
183         self.mapfrom[self.MAP_LINUX][linux] = linux
184
185         return linux
186
187
188     def load_data(self, row, linux):
189         for mapname in self.CODE_COLUMNS:
190             if mapname == self.MAP_LINUX:
191                 continue
192
193             col = self.CODE_COLUMNS[mapname]
194             val = row[col]
195
196             if val == "":
197                 continue
198
199             if val.startswith("0x"):
200                 val = int(val, 16)
201             else:
202                 val = int(val, 10)
203
204             self.mapto[mapname][linux] = val
205             self.mapfrom[mapname][val] = linux
206
207     def load_names(self, row, linux):
208         for mapname in self.NAME_COLUMNS:
209             col = self.NAME_COLUMNS[mapname]
210             val = row[col]
211
212             if val == "":
213                 continue
214
215             self.mapname[mapname][linux] = val
216
217
218     def derive_data(self, row, linux):
219         # Xorg KBD is XKBD XT offset by 8
220         if linux in self.mapto[self.MAP_XKBDXT]:
221             xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8
222             self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgkbd
223             self.mapfrom[self.MAP_XORGXQUARTZ][xorgkbd] = linux
224
225         # Xorg evdev is Linux offset by 8
226         self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8
227         self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux
228
229         # Xorg XQuartx is OS-X offset by 8
230         if linux in self.mapto[self.MAP_OSX]:
231             xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8
232             self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz
233             self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux
234
235         # Xorg Xwin (aka Cygwin) is XWin XT offset by 8
236         if linux in self.mapto[self.MAP_XWINXT]:
237             xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8
238             self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin
239             self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux
240
241
242         # RFB keycodes are XT kbd keycodes with a slightly
243         # different encoding of 0xe0 scan codes. RFB uses
244         # the high bit of the first byte, instead of the low
245         # bit of the second byte
246         if linux in self.mapto[self.MAP_XTKBD]:
247             xtkbd = self.mapto[self.MAP_XTKBD][linux]
248             if xtkbd != 0:
249                 rfb = ((xtkbd & 0x100) >> 1) | (xtkbd & 0x7f)
250             else:
251                 rfb = 0
252             self.mapto[self.MAP_RFB][linux] = rfb
253             self.mapfrom[self.MAP_RFB][rfb] = linux
254
255 class LanguageGenerator(object):
256
257     def generate_header(self, database, args):
258         today = time.strftime("%Y-%m-%d %H:%M")
259         self._boilerplate([
260             "This file is auto-generated from keymaps.csv on %s" % today,
261             "Database checksum sha256(%s)" % database.mapchecksum,
262             "To re-generate, run:",
263             "  %s" % args,
264         ])
265
266     def generate_code_map(self, varname, database, frommapname, tomapname):
267         if frommapname not in database.mapfrom:
268             raise Exception("Unknown map %s, expected %s",
269                             frommapname, ",".join(database.mapfrom.keys()))
270         if tomapname not in database.mapto:
271             raise Exception("Unknown map %s, expected %s",
272                             tomapname, ",".join(database.mapto.keys()))
273
274         tolinux = database.mapfrom[frommapname]
275         fromlinux = database.mapto[tomapname]
276
277         if varname is None:
278             varname = "code_map_%s_to_%s" % (frommapname, tomapname)
279
280         frommax = 0
281         for key in tolinux.keys():
282             if key > frommax:
283                 frommax = key
284
285         self._array_start_code(varname, frommax + 1)
286         for src in range(frommax + 1):
287             linux = tolinux.get(src, None)
288             if linux is None:
289                 dst = None
290             else:
291                 dst = fromlinux.get(linux, None)
292
293             comment = "%s -> %s -> %s" % (self._label(database, frommapname, src),
294                                           self._label(database, Database.MAP_LINUX, linux),
295                                           self._label(database, tomapname, dst))
296             self._array_entry_code(src, dst, comment)
297         self._array_end()                                        
298
299     def generate_code_table(self, varname, database, mapname):
300         if mapname not in database.mapto:
301             raise Exception("Unknown map %s, expected %s",
302                             mapname, ",".join(database.mapto.keys()))
303
304         keys = database.mapto[Database.MAP_LINUX].keys()
305         keys.sort()
306         names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
307
308         if varname is None:
309             varname = "code_table_%s" % mapname
310
311         self._array_start_code(varname, len(keys))
312
313         for i in range(len(keys)):
314             key = keys[i]
315             dst = database.mapto[mapname].get(key, None)
316             self._array_entry_code(i, dst, names[i])
317
318         self._array_end()
319
320     def generate_name_map(self, varname, database, frommapname, tomapname):
321         if frommapname not in database.mapfrom:
322             raise Exception("Unknown map %s, expected %s",
323                             frommapname, ",".join(database.mapfrom.keys()))
324         if tomapname not in database.mapname:
325             raise Exception("Unknown map %s, expected %s",
326                             tomapname, ",".join(database.mapname.keys()))
327
328         tolinux = database.mapfrom[frommapname]
329         fromlinux = database.mapname[tomapname]
330
331         if varname is None:
332             varname = "name_map_%s_to_%s" % (frommapname, tomapname)
333
334         frommax = 0
335         for key in tolinux.keys():
336             if key > frommax:
337                 frommax = key
338
339         self._array_start_name(varname, frommax + 1)
340
341         for src in range(frommax + 1):
342             linux = tolinux.get(src, None)
343             if linux is None:
344                 dst = None
345             else:
346                 dst = fromlinux.get(linux, None)
347
348             comment = "%s -> %s -> %s" % (self._label(database, frommapname, src),
349                                           self._label(database, Database.MAP_LINUX, linux),
350                                           self._label(database, tomapname, dst))
351             self._array_entry_name(src, dst, comment)
352         self._array_end()                                        
353
354     def generate_name_table(self, varname, database, mapname):
355         if mapname not in database.mapname:
356             raise Exception("Unknown map %s, expected %s",
357                             mapname, ",".join(database.mapname.keys()))
358
359         keys = database.mapto[Database.MAP_LINUX].keys()
360         keys.sort()
361         names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
362
363         if varname is None:
364             varname = "name_table_%s" % mapname
365
366         self._array_start_name(varname, len(keys))
367
368         for i in range(len(keys)):
369             key = keys[i]
370             dst = database.mapname[mapname].get(key, None)
371             self._array_entry_name(i, dst, names[i])
372
373         self._array_end()
374
375     def _label(self, database, mapname, val):
376         if mapname in database.mapname:
377             return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(val, "unnamed"))
378         else:
379             return "%s:%s" % (mapname, val)
380
381
382 class CLanguageGenerator(LanguageGenerator):
383
384     def __init__(self, inttypename, strtypename):
385         self.inttypename = inttypename
386         self.strtypename = strtypename
387
388     def _boilerplate(self, lines):
389         print("/*")
390         for line in lines:
391             print(" * %s" % line)
392         print("*/")
393
394     def _array_start_code(self, varname, length):
395         print("const %s %s[%d] = {" % (self.inttypename, varname, length))
396
397     def _array_start_name(self, varname, length):
398         print("const %s %s[%d] = {" % (self.strtypename, varname, length))
399
400     def _array_end(self):
401         print("};")
402
403     def _array_entry_code(self, index, value, comment):
404         if value is not None:
405             print("  [0x%x] = 0x%x, /* %s */" % (index, value, comment))
406
407     def _array_entry_name(self, index, value, comment):
408         if value is not None:
409             print("  [0x%x] = \"%s\", /* %s */" % (index, value, comment))
410
411 class StdCLanguageGenerator(CLanguageGenerator):
412
413     def __init__(self):
414         super(StdCLanguageGenerator, self).__init__("unsigned short", "char *")
415
416 class GLib2LanguageGenerator(CLanguageGenerator):
417
418     def __init__(self):
419         super(GLib2LanguageGenerator, self).__init__("guint16", "gchar *")
420
421 class PythonLanguageGenerator(LanguageGenerator):
422
423     def _boilerplate(self, lines):
424         print("#")
425         for line in lines:
426             print("# %s" % line)
427         print("#")
428
429     def _array_start_code(self, varname, length):
430         print("%s = [" % varname)
431
432     def _array_start_name(self, varname, length):
433         print("%s = [" % varname)
434
435     def _array_end(self):
436         print("]")
437
438     def _array_entry_code(self, index, value, comment):
439         if value is None:
440             print("  None, # %s" % (comment))
441         else:
442             print("  0x%x, # %s" % (value, comment))
443
444     def _array_entry_name(self, index, value, comment):
445         if value is None:
446             print("  None, # %s" % (comment))
447         else:
448             print("  \"%s\", # %s" % (value, comment))
449
450 class PerlLanguageGenerator(LanguageGenerator):
451
452     def _boilerplate(self, lines):
453         print("#")
454         for line in lines:
455             print("# %s" % line)
456         print("#")
457
458     def _array_start_code(self, varname, length):
459         print("my @%s = (" % varname)
460
461     def _array_start_name(self, varname, length):
462         print("my @%s = (" % varname)
463
464     def _array_end(self):
465         print(");")
466
467     def _array_entry_code(self, index, value, comment):
468         if value is None:
469             print("  undef, # %s" % (comment))
470         else:
471             print("  0x%x, # %s" % (value, comment))
472
473     def _array_entry_name(self, index, value, comment):
474         if value is None:
475             print("  undef, # %s" % (comment))
476         else:
477             print("  \"%s\", # %s" % (value, comment))
478
479 GENERATORS = {
480     "stdc": StdCLanguageGenerator(),
481     "glib2": GLib2LanguageGenerator(),
482     "python2": PythonLanguageGenerator(),
483     "python3": PythonLanguageGenerator(),
484     "perl": PerlLanguageGenerator(),
485 }
486
487 def code_map(args):
488     database = Database()
489     database.load(args.keymaps)
490
491     cliargs = ["keymap-gen", "--lang=%s" % args.lang]
492     if args.varname is not None:
493         cliargs.append("--varname=%s" % args.varname)
494     cliargs.extend(["code-map", "keymaps.csv", args.frommapname, args.tomapname])
495     GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
496
497     GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname)
498
499 def code_table(args):
500     database = Database()
501     database.load(args.keymaps)
502
503     cliargs = ["keymap-gen", "--lang=%s" % args.lang]
504     if args.varname is not None:
505         cliargs.append("--varname=%s" % args.varname)
506     cliargs.extend(["code-table", "keymaps.csv", args.mapname])
507     GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
508
509     GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname)
510
511 def name_map(args):
512     database = Database()
513     database.load(args.keymaps)
514
515     cliargs = ["keymap-gen", "--lang=%s" % args.lang]
516     if args.varname is not None:
517         cliargs.append("--varname=%s" % args.varname)
518     cliargs.extend(["name-map", "keymaps.csv", args.frommapname, args.tomapname])
519     GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
520
521     GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname)
522
523 def name_table(args):
524     database = Database()
525     database.load(args.keymaps)
526
527
528     cliargs = ["keymap-gen", "--lang=%s" % args.lang]
529     if args.varname is not None:
530         cliargs.append("--varname=%s" % args.varname)
531     cliargs.extend(["name-table", "keymaps.csv", args.mapname])
532     GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
533
534     GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname)
535
536 def main():
537     parser = argparse.ArgumentParser()
538
539     parser.add_argument("--lang", default="stdc",
540                         help="Output language, %s" % ",".join(GENERATORS.keys()))
541     parser.add_argument("--varname", default=None,
542                         help="Data variable name")
543
544     subparsers = parser.add_subparsers(help="sub-command help")
545
546     codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables")
547     codemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
548     codemapparser.add_argument("frommapname", help="Source code table name")
549     codemapparser.add_argument("tomapname", help="Target code table name")
550     codemapparser.set_defaults(func=code_map)
551
552     codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table")
553     codetableparser.add_argument("keymaps", help="Path to keymap CSV data file")
554     codetableparser.add_argument("mapname", help="Code table name")
555     codetableparser.set_defaults(func=code_table)
556
557     namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names")
558     namemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
559     namemapparser.add_argument("frommapname", help="Source code table name")
560     namemapparser.add_argument("tomapname", help="Target name table name")
561     namemapparser.set_defaults(func=name_map)
562
563     nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table")
564     nametableparser.add_argument("keymaps", help="Path to keymap CSV data file")
565     nametableparser.add_argument("mapname", help="Name table name")
566     nametableparser.set_defaults(func=name_table)
567
568     args = parser.parse_args()
569     args.func(args)
570
571
572 main()