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 # The function generating the progress bar (if any).
21 progress_bar = lambda x, desc: x
22 if os.isatty(sys.stdout.fileno()):
32 def xkbcommontool(rmlvo):
34 r = rmlvo.get('r', 'evdev')
35 m = rmlvo.get('m', 'pc105')
36 l = rmlvo.get('l', 'us')
37 v = rmlvo.get('v', None)
38 o = rmlvo.get('o', None)
46 args += ['--variant', v]
48 args += ['--options', o]
53 print(':: {}'.format(' '.join(args)), file=out)
56 output = subprocess.check_output(args, stderr=subprocess.STDOUT)
58 print(output.decode('utf-8'), file=out)
59 except subprocess.CalledProcessError as err:
60 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
61 print(err.output.decode('utf-8'), file=out)
64 return success, out.getvalue()
65 except KeyboardInterrupt:
71 r = rmlvo.get('r', 'evdev')
72 m = rmlvo.get('m', 'pc105')
73 l = rmlvo.get('l', 'us')
74 v = rmlvo.get('v', None)
75 o = rmlvo.get('o', None)
76 args = ['setxkbmap', '-print']
79 args.append('{}'.format(r))
82 args.append('{}'.format(m))
84 args.append('-layout')
85 args.append('{}'.format(l))
87 args.append('-option')
88 args.append('{}'.format(o))
93 print(':: {}'.format(' '.join(args)), file=out)
96 xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
98 setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
99 xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
100 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
101 setxkbmap.stdout.close()
102 stdout, stderr = xkbcomp.communicate()
103 if xkbcomp.returncode != 0:
104 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
106 if xkbcomp.returncode != 0 or verbose:
107 print(stdout.decode('utf-8'), file=out)
108 print(stderr.decode('utf-8'), file=out)
110 # This catches setxkbmap errors.
111 except subprocess.CalledProcessError as err:
112 print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
113 print(err.output.decode('utf-8'), file=out)
116 return success, out.getvalue()
117 except KeyboardInterrupt:
122 root = ET.fromstring(open(path).read())
123 layouts = root.findall('layoutList/layout')
127 for e in root.findall('optionList/group/option/configItem/name')
132 layout = l.find('configItem/name').text
133 combos.append({'l': layout})
135 variants = l.findall('variantList/variant')
137 variant = v.find('configItem/name').text
139 combos.append({'l': layout, 'v': variant})
140 for option in options:
141 combos.append({'l': layout, 'v': variant, 'o': option})
146 def run(combos, tool, njobs):
148 with Pool(njobs) as p:
149 results = p.imap_unordered(tool, combos)
150 for r in progress_bar(results, 'testing'):
155 print(output, file=sys.stdout if success else sys.stderr)
162 'libxkbcommon': xkbcommontool,
166 parser = argparse.ArgumentParser(
167 description='Tool to test all layout/variant/option combinations.'
169 parser.add_argument('path', metavar='/path/to/evdev.xml',
171 default=DEFAULT_RULES_XML,
172 help='Path to xkeyboard-config\'s evdev.xml')
173 parser.add_argument('--tool', choices=tools.keys(),
174 type=str, default='libxkbcommon',
175 help='parsing tool to use')
176 parser.add_argument('--jobs', '-j', type=int,
177 default=os.cpu_count() * 4,
178 help='number of processes to use')
179 args = parser.parse_args()
181 tool = tools[args.tool]
183 combos = parse(args.path)
184 failed = run(combos, tool, args.jobs)
188 if __name__ == '__main__':
191 except KeyboardInterrupt:
192 print('Exiting after Ctrl+C')