2018-08-13 19:16:30 -06:00
|
|
|
#!/usr/bin/env python
|
2019-10-29 19:15:49 -06:00
|
|
|
import argparse
|
2018-08-13 19:16:30 -06:00
|
|
|
import sys
|
|
|
|
import subprocess
|
|
|
|
import os
|
2019-10-29 00:06:10 -06:00
|
|
|
import io
|
2018-08-13 19:16:30 -06:00
|
|
|
import xml.etree.ElementTree as ET
|
2019-10-29 00:06:10 -06:00
|
|
|
from multiprocessing import Pool
|
2018-08-13 19:16:30 -06:00
|
|
|
|
|
|
|
|
|
|
|
verbose = True
|
|
|
|
|
|
|
|
DEFAULT_RULES_XML = '@XKB_CONFIG_ROOT@/rules/evdev.xml'
|
|
|
|
|
|
|
|
# Meson needs to fill this in so we can call the tool in the buildir.
|
2019-10-29 20:03:48 -06:00
|
|
|
EXTRA_PATH = '@MESON_BUILD_ROOT@'
|
2018-08-13 19:16:30 -06:00
|
|
|
os.environ['PATH'] = ':'.join([EXTRA_PATH, os.getenv('PATH')])
|
|
|
|
|
|
|
|
|
2019-10-29 20:03:48 -06:00
|
|
|
def noop_progress_bar(x, desc):
|
|
|
|
return x
|
|
|
|
|
|
|
|
|
2018-08-13 19:16:30 -06:00
|
|
|
# The function generating the progress bar (if any).
|
2019-10-29 20:03:48 -06:00
|
|
|
progress_bar = noop_progress_bar
|
2018-08-13 19:16:30 -06:00
|
|
|
if os.isatty(sys.stdout.fileno()):
|
|
|
|
try:
|
|
|
|
from tqdm import tqdm
|
|
|
|
progress_bar = tqdm
|
|
|
|
|
|
|
|
verbose = False
|
|
|
|
except ImportError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
def xkbcommontool(rmlvo):
|
2018-08-13 19:16:30 -06:00
|
|
|
try:
|
2019-10-29 19:22:49 -06:00
|
|
|
r = rmlvo.get('r', 'evdev')
|
|
|
|
m = rmlvo.get('m', 'pc105')
|
|
|
|
l = rmlvo.get('l', 'us')
|
|
|
|
v = rmlvo.get('v', None)
|
|
|
|
o = rmlvo.get('o', None)
|
|
|
|
args = [
|
|
|
|
'rmlvo-to-keymap',
|
|
|
|
'--rules', r,
|
|
|
|
'--model', m,
|
|
|
|
'--layout', l,
|
|
|
|
]
|
|
|
|
if v is not None:
|
|
|
|
args += ['--variant', v]
|
|
|
|
if o is not None:
|
|
|
|
args += ['--options', o]
|
|
|
|
|
|
|
|
success = True
|
|
|
|
out = io.StringIO()
|
2018-08-13 19:16:30 -06:00
|
|
|
if verbose:
|
2019-10-29 19:22:49 -06:00
|
|
|
print(':: {}'.format(' '.join(args)), file=out)
|
|
|
|
|
|
|
|
try:
|
2019-10-31 17:54:29 -06:00
|
|
|
output = subprocess.check_output(args, stderr=subprocess.STDOUT,
|
|
|
|
universal_newlines=True)
|
2019-10-29 19:22:49 -06:00
|
|
|
if verbose:
|
2019-10-31 17:54:29 -06:00
|
|
|
print(output, file=out)
|
2019-10-29 19:22:49 -06:00
|
|
|
except subprocess.CalledProcessError as err:
|
|
|
|
print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
|
2019-10-31 17:54:29 -06:00
|
|
|
print(err.output, file=out)
|
2019-10-29 19:22:49 -06:00
|
|
|
success = False
|
2018-08-13 19:16:30 -06:00
|
|
|
|
2019-10-29 19:22:49 -06:00
|
|
|
return success, out.getvalue()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
2018-08-13 19:16:30 -06:00
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
|
|
|
|
def xkbcomp(rmlvo):
|
2018-08-13 19:16:30 -06:00
|
|
|
try:
|
2019-10-29 19:22:49 -06:00
|
|
|
r = rmlvo.get('r', 'evdev')
|
|
|
|
m = rmlvo.get('m', 'pc105')
|
|
|
|
l = rmlvo.get('l', 'us')
|
|
|
|
v = rmlvo.get('v', None)
|
|
|
|
o = rmlvo.get('o', None)
|
|
|
|
args = ['setxkbmap', '-print']
|
|
|
|
if r is not None:
|
|
|
|
args.append('-rules')
|
|
|
|
args.append('{}'.format(r))
|
|
|
|
if m is not None:
|
|
|
|
args.append('-model')
|
|
|
|
args.append('{}'.format(m))
|
|
|
|
if l is not None:
|
|
|
|
args.append('-layout')
|
|
|
|
args.append('{}'.format(l))
|
|
|
|
if o is not None:
|
|
|
|
args.append('-option')
|
|
|
|
args.append('{}'.format(o))
|
|
|
|
|
|
|
|
success = True
|
|
|
|
out = io.StringIO()
|
|
|
|
if verbose:
|
|
|
|
print(':: {}'.format(' '.join(args)), file=out)
|
|
|
|
|
|
|
|
try:
|
|
|
|
xkbcomp_args = ['xkbcomp', '-xkb', '-', '-']
|
|
|
|
|
|
|
|
setxkbmap = subprocess.Popen(args, stdout=subprocess.PIPE)
|
|
|
|
xkbcomp = subprocess.Popen(xkbcomp_args, stdin=setxkbmap.stdout,
|
2019-10-31 17:54:29 -06:00
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
|
|
universal_newlines=True)
|
2019-10-29 19:22:49 -06:00
|
|
|
setxkbmap.stdout.close()
|
|
|
|
stdout, stderr = xkbcomp.communicate()
|
|
|
|
if xkbcomp.returncode != 0:
|
|
|
|
print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
|
|
|
|
success = False
|
|
|
|
if xkbcomp.returncode != 0 or verbose:
|
2019-10-31 17:54:29 -06:00
|
|
|
print(stdout, file=out)
|
|
|
|
print(stderr, file=out)
|
2019-10-29 19:22:49 -06:00
|
|
|
|
|
|
|
# This catches setxkbmap errors.
|
|
|
|
except subprocess.CalledProcessError as err:
|
2019-10-29 00:06:10 -06:00
|
|
|
print('ERROR: Failed to compile: {}'.format(' '.join(args)), file=out)
|
2019-10-31 17:54:29 -06:00
|
|
|
print(err.output, file=out)
|
2019-10-29 00:06:10 -06:00
|
|
|
success = False
|
2018-08-13 19:16:30 -06:00
|
|
|
|
2019-10-29 19:22:49 -06:00
|
|
|
return success, out.getvalue()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
2018-08-13 19:16:30 -06:00
|
|
|
|
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
def parse(path):
|
|
|
|
root = ET.fromstring(open(path).read())
|
2018-08-13 19:16:30 -06:00
|
|
|
layouts = root.findall('layoutList/layout')
|
|
|
|
|
|
|
|
options = [
|
|
|
|
e.text
|
|
|
|
for e in root.findall('optionList/group/option/configItem/name')
|
|
|
|
]
|
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
combos = []
|
|
|
|
for l in layouts:
|
2018-08-13 19:16:30 -06:00
|
|
|
layout = l.find('configItem/name').text
|
2019-10-29 00:06:10 -06:00
|
|
|
combos.append({'l': layout})
|
2018-08-13 19:16:30 -06:00
|
|
|
|
|
|
|
variants = l.findall('variantList/variant')
|
2019-10-29 00:06:10 -06:00
|
|
|
for v in variants:
|
2018-08-13 19:16:30 -06:00
|
|
|
variant = v.find('configItem/name').text
|
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
combos.append({'l': layout, 'v': variant})
|
|
|
|
for option in options:
|
|
|
|
combos.append({'l': layout, 'v': variant, 'o': option})
|
|
|
|
|
|
|
|
return combos
|
|
|
|
|
|
|
|
|
|
|
|
def run(combos, tool, njobs):
|
|
|
|
failed = False
|
|
|
|
with Pool(njobs) as p:
|
|
|
|
results = p.imap_unordered(tool, combos)
|
|
|
|
for r in progress_bar(results, 'testing'):
|
|
|
|
success, output = r
|
|
|
|
if not success:
|
|
|
|
failed = True
|
|
|
|
if output:
|
2019-10-29 18:53:58 -06:00
|
|
|
print(output, file=sys.stdout if success else sys.stderr)
|
2019-10-29 00:06:10 -06:00
|
|
|
|
|
|
|
return failed
|
2018-08-13 19:16:30 -06:00
|
|
|
|
|
|
|
|
|
|
|
def main(args):
|
2019-10-29 19:15:49 -06:00
|
|
|
tools = {
|
|
|
|
'libxkbcommon': xkbcommontool,
|
|
|
|
'xkbcomp': xkbcomp,
|
|
|
|
}
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description='Tool to test all layout/variant/option combinations.'
|
|
|
|
)
|
|
|
|
parser.add_argument('path', metavar='/path/to/evdev.xml',
|
|
|
|
nargs='?', type=str,
|
|
|
|
default=DEFAULT_RULES_XML,
|
|
|
|
help='Path to xkeyboard-config\'s evdev.xml')
|
|
|
|
parser.add_argument('--tool', choices=tools.keys(),
|
|
|
|
type=str, default='libxkbcommon',
|
|
|
|
help='parsing tool to use')
|
2019-10-29 00:06:10 -06:00
|
|
|
parser.add_argument('--jobs', '-j', type=int,
|
|
|
|
default=os.cpu_count() * 4,
|
|
|
|
help='number of processes to use')
|
2019-10-29 19:15:49 -06:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
tool = tools[args.tool]
|
|
|
|
|
2019-10-29 00:06:10 -06:00
|
|
|
combos = parse(args.path)
|
|
|
|
failed = run(combos, tool, args.jobs)
|
|
|
|
sys.exit(failed)
|
2018-08-13 19:16:30 -06:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2019-10-29 19:22:49 -06:00
|
|
|
try:
|
|
|
|
main(sys.argv)
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print('Exiting after Ctrl+C')
|