7 import xml.etree.ElementTree as ET
8 from multiprocessing import Pool
13 DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
15 # Meson needs to fill this in so we can call the tool in the buildir.
16 EXTRA_PATH = '@MESON_BUILD_ROOT@'
17 os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
20 def noop_progress_bar(x, total):
24 # The function generating the progress bar (if any).
25 progress_bar = noop_progress_bar
26 if os.isatty(sys.stdout.fileno()):
36 def xkbcommontool(rmlvo):
38 r = rmlvo.get('r', 'evdev')
39 m = rmlvo.get('m', 'pc105')
40 l = rmlvo.get('l', 'us')
41 v = rmlvo.get('v', None)
42 o = rmlvo.get('o', None)
44 'xkbcli-compile-keymap', # this is run in the builddir
51 args += ['--variant', v]
53 args += ['--options', o]
58 print(':: {}'.format(' '.join(args)), file=out)
61 output = subprocess.check_output(args, stderr=subprocess.STDOUT,
62 universal_newlines=True)
64 print(output, file=out)
66 if "unrecognized keysym" in output:
67 for line in output.split('\n'):
68 if "unrecognized keysym" in line:
69 print('ERROR: {}'.format(line))
71 except subprocess.CalledProcessError as err:
72 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
73 print(err.output, file=out)
76 return success, out.getvalue()
77 except KeyboardInterrupt:
83 r = rmlvo.get('r', 'evdev')
84 m = rmlvo.get('m', 'pc105')
85 l = rmlvo.get('l', 'us')
86 v = rmlvo.get('v', None)
87 o = rmlvo.get('o', None)
88 args = ['setxkbmap', '-print']
91 args.append('{}'.format(r))
94 args.append('{}'.format(m))
96 args.append('-layout')
97 args.append('{}'.format(l))
99 args.append('-variant')
100 args.append('{}'.format(v))
102 args.append('-option')
103 args.append('{}'.format(o))
108 print(':: {}'.format(' '.join(args)), file=out)
111 xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
113 setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
114 xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
115 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
116 universal_newlines=True)
117 setxkbmap.stdout.close()
118 stdout, stderr = xkbcomp.communicate()
119 if xkbcomp.returncode != 0:
120 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
122 if xkbcomp.returncode != 0 or verbose:
123 print(stdout, file=out)
124 print(stderr, file=out)
126 # This catches setxkbmap errors.
127 except subprocess.CalledProcessError as err:
128 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
129 print(err.output, file=out)
132 return success, out.getvalue()
133 except KeyboardInterrupt:
138 root = ET.fromstring(open(path).read())
139 layouts = root.findall('layoutList/layout')
143 for e in root.findall('optionList/group/option/configItem/name')
148 layout = l.find('configItem/name').text
149 combos.append({'l': layout})
151 variants = l.findall('variantList/variant')
153 variant = v.find('configItem/name').text
155 combos.append({'l': layout, 'v': variant})
156 for option in options:
157 combos.append({'l': layout, 'v': variant, 'o': option})
162 def run(combos, tool, njobs):
164 with Pool(njobs) as p:
165 results = p.imap_unordered(tool, combos)
166 for success, output in progress_bar(results, total=len(combos)):
170 print(output, file=sys.stdout if success else sys.stderr)
176 'libxkbcommon': xkbcommontool,
180 parser = argparse.ArgumentParser(
181 description='Tool to test all layout/variant/option combinations.'
183 parser.add_argument('path', metavar='/path/to/evdev.xml',
185 default=DEFAULT_RULES_XML,
186 help='Path to xkeyboard-config\'s evdev.xml')
187 parser.add_argument('--tool', choices=tools.keys(),
188 type=str, default='libxkbcommon',
189 help='parsing tool to use')
190 parser.add_argument('--jobs', '-j', type=int,
191 default=os.cpu_count() * 4,
192 help='number of processes to use')
193 args = parser.parse_args()
195 tool = tools[args.tool]
197 combos = parse(args.path)
198 failed = run(combos, tool, args.jobs)
202 if __name__ == '__main__':
205 except KeyboardInterrupt:
206 print('Exiting after Ctrl+C')