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')])
21 return s.replace('"', '\\"')
24 # The function generating the progress bar (if any).
25 def create_progress_bar(verbose):
26 def noop_progress_bar(x, total):
29 progress_bar = noop_progress_bar
30 if not verbose and os.isatty(sys.stdout.fileno()):
40 def xkbcommontool(rmlvo):
42 r = rmlvo.get('r', 'evdev')
43 m = rmlvo.get('m', 'pc105')
44 l = rmlvo.get('l', 'us')
45 v = rmlvo.get('v', None)
46 o = rmlvo.get('o', None)
48 'xkbcli-compile-keymap', # this is run in the builddir
55 args += ['--variant', v]
57 args += ['--options', o]
62 print(':: {}'.format(' '.join(args)), file=out)
65 output = subprocess.check_output(args, stderr=subprocess.STDOUT,
66 universal_newlines=True)
68 print(output, file=out)
70 if "unrecognized keysym" in output:
71 for line in output.split('\n'):
72 if "unrecognized keysym" in line:
73 print('ERROR: {}'.format(line))
75 except subprocess.CalledProcessError as err:
76 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
77 print(err.output, file=out)
80 return success, out.getvalue()
81 except KeyboardInterrupt:
87 r = rmlvo.get('r', 'evdev')
88 m = rmlvo.get('m', 'pc105')
89 l = rmlvo.get('l', 'us')
90 v = rmlvo.get('v', None)
91 o = rmlvo.get('o', None)
92 args = ['setxkbmap', '-print']
95 args.append('{}'.format(r))
98 args.append('{}'.format(m))
100 args.append('-layout')
101 args.append('{}'.format(l))
103 args.append('-variant')
104 args.append('{}'.format(v))
106 args.append('-option')
107 args.append('{}'.format(o))
112 print(':: {}'.format(' '.join(args)), file=out)
115 xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
117 setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
118 xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
119 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
120 universal_newlines=True)
121 setxkbmap.stdout.close()
122 stdout, stderr = xkbcomp.communicate()
123 if xkbcomp.returncode != 0:
124 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
126 if xkbcomp.returncode != 0 or verbose:
127 print(stdout, file=out)
128 print(stderr, file=out)
130 # This catches setxkbmap errors.
131 except subprocess.CalledProcessError as err:
132 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
133 print(err.output, file=out)
136 return success, out.getvalue()
137 except KeyboardInterrupt:
142 root = ET.fromstring(open(path).read())
143 layouts = root.findall('layoutList/layout')
147 for e in root.findall('optionList/group/option/configItem/name')
152 layout = l.find('configItem/name').text
153 combos.append({'l': layout})
155 variants = l.findall('variantList/variant')
157 variant = v.find('configItem/name').text
159 combos.append({'l': layout, 'v': variant})
160 for option in options:
161 combos.append({'l': layout, 'v': variant, 'o': option})
166 def run(combos, tool, njobs):
168 with Pool(njobs) as p:
169 results = p.imap_unordered(tool, combos)
170 for success, output in progress_bar(results, total=len(combos)):
174 print(output, file=sys.stdout if success else sys.stderr)
183 'libxkbcommon': xkbcommontool,
187 parser = argparse.ArgumentParser(
188 description='Tool to test all layout/variant/option combinations.'
190 parser.add_argument('path', metavar='/path/to/evdev.xml',
192 default=DEFAULT_RULES_XML,
193 help='Path to xkeyboard-config\'s evdev.xml')
194 parser.add_argument('--tool', choices=tools.keys(),
195 type=str, default='libxkbcommon',
196 help='parsing tool to use')
197 parser.add_argument('--jobs', '-j', type=int,
198 default=os.cpu_count() * 4,
199 help='number of processes to use')
200 parser.add_argument('--verbose', '-v', default=False, action="store_true")
201 args = parser.parse_args()
203 verbose = args.verbose
204 progress_bar = create_progress_bar(verbose)
206 tool = tools[args.tool]
208 combos = parse(args.path)
209 failed = run(combos, tool, args.jobs)
213 if __name__ == '__main__':
216 except KeyboardInterrupt:
217 print('Exiting after Ctrl+C')