7 import xml.etree.ElementTree as ET
12 DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
14 # Meson needs to fill this in so we can call the tool in the buildir.
15 EXTRA_PATH = '@MESON_BUILD_ROOT@'
16 os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
20 return s.replace('"', '\\"')
23 # The function generating the progress bar (if any).
24 def create_progress_bar(verbose):
25 def noop_progress_bar(x, total):
28 progress_bar = noop_progress_bar
29 if not verbose and os.isatty(sys.stdout.fileno()):
40 def __init__(self, r, m, l, v, o):
47 self.exitstatus = 77 # default to skipped
49 self.keymap = None # The fully compiled keymap
53 return self.rules, self.model, self.layout, self.variant, self.option
57 rmlvo = [x or "" for x in self.rmlvo]
58 rmlvo = ', '.join([f'"{x}"' for x in rmlvo])
59 s.append(f'- rmlvo: [{rmlvo}]')
60 s.append(f' cmd: "{escape(self.command)}"')
61 s.append(f' status: {self.exitstatus}')
63 s.append(f' error: "{escape(self.error.strip())}"')
67 raise NotImplementedError
70 class XkbCompInvocation(Invocation):
72 r, m, l, v, o = self.rmlvo
73 args = ['setxkbmap', '-print']
76 args.append('{}'.format(r))
79 args.append('{}'.format(m))
81 args.append('-layout')
82 args.append('{}'.format(l))
84 args.append('-variant')
85 args.append('{}'.format(v))
87 args.append('-option')
88 args.append('{}'.format(o))
90 xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
92 self.command = " ".join(args + ["|"] + xkbcomp_args)
94 setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE,
95 stderr=subprocess.PIPE, universal_newlines=True)
96 stdout, stderr = setxkbmap.communicate()
97 if "Cannot open display" in stderr:
101 xkbcomp = subprocess.Popen(xkbcomp_args, stdin=subprocess.PIPE,
102 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
103 universal_newlines=True)
104 stdout, stderr = xkbcomp.communicate(stdout)
105 if xkbcomp.returncode != 0:
106 self.error = "failed to compile keymap"
107 self.exitstatus = xkbcomp.returncode
113 class XkbcommonInvocation(Invocation):
115 r, m, l, v, o = self.rmlvo
117 'xkbcli-compile-keymap', # this is run in the builddir
124 args += ['--variant', v]
126 args += ['--options', o]
128 self.command = " ".join(args)
130 output = subprocess.check_output(args, stderr=subprocess.STDOUT,
131 universal_newlines=True)
132 if "unrecognized keysym" in output:
133 for line in output.split('\n'):
134 if "unrecognized keysym" in line:
136 self.exitstatus = 99 # tool doesn't generate this one
140 except subprocess.CalledProcessError as err:
141 self.error = "failed to compile keymap"
142 self.exitstatus = err.returncode
145 def xkbcommontool(rmlvo):
147 r = rmlvo.get('r', 'evdev')
148 m = rmlvo.get('m', 'pc105')
149 l = rmlvo.get('l', 'us')
150 v = rmlvo.get('v', None)
151 o = rmlvo.get('o', None)
152 tool = XkbcommonInvocation(r, m, l, v, o)
155 except KeyboardInterrupt:
161 r = rmlvo.get('r', 'evdev')
162 m = rmlvo.get('m', 'pc105')
163 l = rmlvo.get('l', 'us')
164 v = rmlvo.get('v', None)
165 o = rmlvo.get('o', None)
166 tool = XkbCompInvocation(r, m, l, v, o)
169 except KeyboardInterrupt:
174 root = ET.fromstring(open(path).read())
175 layouts = root.findall('layoutList/layout')
179 for e in root.findall('optionList/group/option/configItem/name')
184 layout = l.find('configItem/name').text
185 combos.append({'l': layout})
187 variants = l.findall('variantList/variant')
189 variant = v.find('configItem/name').text
191 combos.append({'l': layout, 'v': variant})
192 for option in options:
193 combos.append({'l': layout, 'v': variant, 'o': option})
198 def run(combos, tool, njobs):
200 with multiprocessing.Pool(njobs) as p:
201 results = p.imap_unordered(tool, combos)
202 for invocation in progress_bar(results, total=len(combos)):
203 if invocation.exitstatus != 0:
207 target = sys.stdout if verbose else None
210 print(invocation, file=target)
220 'libxkbcommon': xkbcommontool,
224 parser = argparse.ArgumentParser(
225 description='Tool to test all layout/variant/option combinations.'
227 parser.add_argument('path', metavar='/path/to/evdev.xml',
229 default=DEFAULT_RULES_XML,
230 help='Path to xkeyboard-config\'s evdev.xml')
231 parser.add_argument('--tool', choices=tools.keys(),
232 type=str, default='libxkbcommon',
233 help='parsing tool to use')
234 parser.add_argument('--jobs', '-j', type=int,
235 default=os.cpu_count() * 4,
236 help='number of processes to use')
237 parser.add_argument('--verbose', '-v', default=False, action="store_true")
238 args = parser.parse_args()
240 verbose = args.verbose
241 progress_bar = create_progress_bar(verbose)
243 tool = tools[args.tool]
245 combos = parse(args.path)
246 failed = run(combos, tool, args.jobs)
250 if __name__ == '__main__':
253 except KeyboardInterrupt:
254 print('# Exiting after Ctrl+C')