From 1dbdef8fdbda33a888882c5a9d0bf9657a3eb7df Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 25 Jan 2021 13:12:25 +1000 Subject: [PATCH] Use python black for all pyhon file formatting Let's enforce a consistent (and verifiable) style everywhere. Signed-off-by: Peter Hutterer --- .gitlab-ci.yml | 7 +- .gitlab-ci/ci.template | 7 +- .gitlab-ci/meson-junit-report.py | 121 +++--- tools/libinput-analyze-per-slot-delta.py | 150 ++++--- tools/libinput-measure-fuzz.py | 385 ++++++++++-------- tools/libinput-measure-touch-size.py | 123 ++++-- tools/libinput-measure-touchpad-pressure.py | 182 ++++++--- tools/libinput-measure-touchpad-size.py | 272 ++++++++----- tools/libinput-measure-touchpad-tap.py | 108 +++-- tools/libinput-record-verify-yaml.py | 420 ++++++++++---------- tools/libinput-replay | 200 ++++++---- tools/test_tool_option_parsing.py | 238 ++++++----- 12 files changed, 1291 insertions(+), 922 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04f235f4..821722d3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -747,13 +747,14 @@ usr-bin-env-python@fedora:33: /bin/false fi -flake8@fedora:33: +python-format@fedora:33: extends: - .fedora-build@template before_script: - - dnf install -y python3-flake8 + - dnf install -y black script: - - flake8-3 --ignore=W501,E501,W504,E741 $(git grep -l '^#!/usr/bin/env python3') + - black $(git grep -l '^#!/usr/bin/env python3') + - git diff --exit-code || (echo "Please run Black against all Python files" && false) # diff --git a/.gitlab-ci/ci.template b/.gitlab-ci/ci.template index 28532542..2d6566a2 100644 --- a/.gitlab-ci/ci.template +++ b/.gitlab-ci/ci.template @@ -542,13 +542,14 @@ usr-bin-env-python@{{distro.name}}:{{version}}: /bin/false fi -flake8@{{distro.name}}:{{version}}: +python-format@{{distro.name}}:{{version}}: extends: - .{{distro.name}}-build@template before_script: - - dnf install -y python3-flake8 + - dnf install -y black script: - - flake8-3 --ignore=W501,E501,W504,E741 $(git grep -l '^#!/usr/bin/env python3') + - black $(git grep -l '^#!/usr/bin/env python3') + - git diff --exit-code || (echo "Please run Black against all Python files" && false) {% endfor %} diff --git a/.gitlab-ci/meson-junit-report.py b/.gitlab-ci/meson-junit-report.py index 542065be..aad52f4f 100755 --- a/.gitlab-ci/meson-junit-report.py +++ b/.gitlab-ci/meson-junit-report.py @@ -12,95 +12,106 @@ import json import sys import xml.etree.ElementTree as ET -aparser = argparse.ArgumentParser(description='Turns a Meson test log into a JUnit report') -aparser.add_argument('--project-name', metavar='NAME', - help='The project name', - default='unknown') -aparser.add_argument('--job-id', metavar='ID', - help='The job ID for the report', - default='Unknown') -aparser.add_argument('--branch', metavar='NAME', - help='Branch of the project being tested', - default='master') -aparser.add_argument('--output', metavar='FILE', - help='The output file, stdout by default', - type=argparse.FileType('w', encoding='UTF-8'), - default=sys.stdout) -aparser.add_argument('infile', metavar='FILE', - help='The input testlog.json, stdin by default', - type=argparse.FileType('r', encoding='UTF-8'), - default=sys.stdin) +aparser = argparse.ArgumentParser( + description="Turns a Meson test log into a JUnit report" +) +aparser.add_argument( + "--project-name", metavar="NAME", help="The project name", default="unknown" +) +aparser.add_argument( + "--job-id", metavar="ID", help="The job ID for the report", default="Unknown" +) +aparser.add_argument( + "--branch", + metavar="NAME", + help="Branch of the project being tested", + default="master", +) +aparser.add_argument( + "--output", + metavar="FILE", + help="The output file, stdout by default", + type=argparse.FileType("w", encoding="UTF-8"), + default=sys.stdout, +) +aparser.add_argument( + "infile", + metavar="FILE", + help="The input testlog.json, stdin by default", + type=argparse.FileType("r", encoding="UTF-8"), + default=sys.stdin, +) args = aparser.parse_args() outfile = args.output -testsuites = ET.Element('testsuites') -testsuites.set('id', '{}/{}'.format(args.job_id, args.branch)) -testsuites.set('package', args.project_name) -testsuites.set('timestamp', datetime.datetime.utcnow().isoformat(timespec='minutes')) +testsuites = ET.Element("testsuites") +testsuites.set("id", "{}/{}".format(args.job_id, args.branch)) +testsuites.set("package", args.project_name) +testsuites.set("timestamp", datetime.datetime.utcnow().isoformat(timespec="minutes")) suites = {} for line in args.infile: data = json.loads(line) - (full_suite, unit_name) = data['name'].split(' / ') - (project_name, suite_name) = full_suite.split(':') + (full_suite, unit_name) = data["name"].split(" / ") + (project_name, suite_name) = full_suite.split(":") - duration = data['duration'] - return_code = data['returncode'] - log = data['stdout'] + duration = data["duration"] + return_code = data["returncode"] + log = data["stdout"] unit = { - 'suite': suite_name, - 'name': unit_name, - 'duration': duration, - 'returncode': return_code, - 'stdout': log, + "suite": suite_name, + "name": unit_name, + "duration": duration, + "returncode": return_code, + "stdout": log, } units = suites.setdefault(suite_name, []) units.append(unit) for name, units in suites.items(): - print('Processing suite {} (units: {})'.format(name, len(units))) + print("Processing suite {} (units: {})".format(name, len(units))) def if_failed(unit): - if unit['returncode'] != 0: + if unit["returncode"] != 0: return True return False def if_succeded(unit): - if unit['returncode'] == 0: + if unit["returncode"] == 0: return True return False successes = list(filter(if_succeded, units)) failures = list(filter(if_failed, units)) - print(' - {}: {} pass, {} fail'.format(name, len(successes), len(failures))) + print(" - {}: {} pass, {} fail".format(name, len(successes), len(failures))) - testsuite = ET.SubElement(testsuites, 'testsuite') - testsuite.set('name', '{}/{}'.format(args.project_name, name)) - testsuite.set('tests', str(len(units))) - testsuite.set('errors', str(len(failures))) - testsuite.set('failures', str(len(failures))) + testsuite = ET.SubElement(testsuites, "testsuite") + testsuite.set("name", "{}/{}".format(args.project_name, name)) + testsuite.set("tests", str(len(units))) + testsuite.set("errors", str(len(failures))) + testsuite.set("failures", str(len(failures))) for unit in successes: - testcase = ET.SubElement(testsuite, 'testcase') - testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) - testcase.set('name', unit['name']) - testcase.set('time', str(unit['duration'])) + testcase = ET.SubElement(testsuite, "testcase") + testcase.set("classname", "{}/{}".format(args.project_name, unit["suite"])) + testcase.set("name", unit["name"]) + testcase.set("time", str(unit["duration"])) for unit in failures: - testcase = ET.SubElement(testsuite, 'testcase') - testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) - testcase.set('name', unit['name']) - testcase.set('time', str(unit['duration'])) + testcase = ET.SubElement(testsuite, "testcase") + testcase.set("classname", "{}/{}".format(args.project_name, unit["suite"])) + testcase.set("name", unit["name"]) + testcase.set("time", str(unit["duration"])) - failure = ET.SubElement(testcase, 'failure') - failure.set('classname', '{}/{}'.format(args.project_name, unit['suite'])) - failure.set('name', unit['name']) - failure.set('type', 'error') - failure.text = unit['stdout'] + failure = ET.SubElement(testcase, "failure") + failure.set("classname", "{}/{}".format(args.project_name, unit["suite"])) + failure.set("name", unit["name"]) + failure.set("type", "error") + failure.text = unit["stdout"] -output = ET.tostring(testsuites, encoding='unicode') +output = ET.tostring(testsuites, encoding="unicode") outfile.write(output) diff --git a/tools/libinput-analyze-per-slot-delta.py b/tools/libinput-analyze-per-slot-delta.py index 1cf181d0..8379ff19 100755 --- a/tools/libinput-analyze-per-slot-delta.py +++ b/tools/libinput-analyze-per-slot-delta.py @@ -36,15 +36,16 @@ import yaml import libevdev -COLOR_RESET = '\x1b[0m' -COLOR_RED = '\x1b[6;31m' +COLOR_RESET = "\x1b[0m" +COLOR_RED = "\x1b[6;31m" -class SlotFormatter(): +class SlotFormatter: width = 16 - def __init__(self, is_absolute=False, resolution=None, - threshold=None, ignore_below=None): + def __init__( + self, is_absolute=False, resolution=None, threshold=None, ignore_below=None + ): self.threshold = threshold self.ignore_below = ignore_below self.resolution = resolution @@ -54,19 +55,19 @@ class SlotFormatter(): self.filtered = False def __str__(self): - return ' | '.join(self.slots) + return " | ".join(self.slots) def format_slot(self, slot): if slot.state == SlotState.BEGIN: - self.slots.append('+++++++'.center(self.width)) + self.slots.append("+++++++".center(self.width)) self.have_data = True elif slot.state == SlotState.END: - self.slots.append('-------'.center(self.width)) + self.slots.append("-------".center(self.width)) self.have_data = True elif slot.state == SlotState.NONE: - self.slots.append(('*' * (self.width - 2)).center(self.width)) + self.slots.append(("*" * (self.width - 2)).center(self.width)) elif not slot.dirty: - self.slots.append(' '.center(self.width)) + self.slots.append(" ".center(self.width)) else: if self.resolution is not None: dx, dy = slot.dx / self.resolution[0], slot.dy / self.resolution[1] @@ -81,35 +82,39 @@ class SlotFormatter(): else: t = t * 180.0 / math.pi - directions = ['↖↑', '↖←', '↙←', '↙↓', '↓↘', '→↘', '→↗', '↑↗'] + directions = ["↖↑", "↖←", "↙←", "↙↓", "↓↘", "→↘", "→↗", "↑↗"] direction = directions[int(t / 45)] elif dy == 0: if dx < 0: - direction = '←←' + direction = "←←" else: - direction = '→→' + direction = "→→" else: if dy < 0: - direction = '↑↑' + direction = "↑↑" else: - direction = '↓↓' + direction = "↓↓" - color = '' - reset = '' + color = "" + reset = "" if not self.is_absolute: if self.ignore_below is not None or self.threshold is not None: dist = math.hypot(dx, dy) if self.ignore_below is not None and dist < self.ignore_below: - self.slots.append(' '.center(self.width)) + self.slots.append(" ".center(self.width)) self.filtered = True return if self.threshold is not None and dist >= self.threshold: color = COLOR_RED reset = COLOR_RESET if isinstance(dx, int) and isinstance(dy, int): - string = "{} {}{:+4d}/{:+4d}{}".format(direction, color, dx, dy, reset) + string = "{} {}{:+4d}/{:+4d}{}".format( + direction, color, dx, dy, reset + ) else: - string = "{} {}{:+3.2f}/{:+03.2f}{}".format(direction, color, dx, dy, reset) + string = "{} {}{:+3.2f}/{:+03.2f}{}".format( + direction, color, dx, dy, reset + ) else: x, y = slot.x, slot.y string = "{} {}{:4d}/{:4d}{}".format(direction, color, x, y, reset) @@ -144,23 +149,46 @@ def main(argv): slots = [] xres, yres = 1, 1 - parser = argparse.ArgumentParser(description="Measure delta between event frames for each slot") - parser.add_argument("--use-mm", action='store_true', help="Use mm instead of device deltas") - parser.add_argument("--use-st", action='store_true', help="Use ABS_X/ABS_Y instead of ABS_MT_POSITION_X/Y") - parser.add_argument("--use-absolute", action='store_true', help="Use absolute coordinates, not deltas") - parser.add_argument("path", metavar="recording", - nargs=1, help="Path to libinput-record YAML file") - parser.add_argument("--threshold", type=float, default=None, help="Mark any delta above this threshold") - parser.add_argument("--ignore-below", type=float, default=None, help="Ignore any delta below this threshold") + parser = argparse.ArgumentParser( + description="Measure delta between event frames for each slot" + ) + parser.add_argument( + "--use-mm", action="store_true", help="Use mm instead of device deltas" + ) + parser.add_argument( + "--use-st", + action="store_true", + help="Use ABS_X/ABS_Y instead of ABS_MT_POSITION_X/Y", + ) + parser.add_argument( + "--use-absolute", + action="store_true", + help="Use absolute coordinates, not deltas", + ) + parser.add_argument( + "path", metavar="recording", nargs=1, help="Path to libinput-record YAML file" + ) + parser.add_argument( + "--threshold", + type=float, + default=None, + help="Mark any delta above this threshold", + ) + parser.add_argument( + "--ignore-below", + type=float, + default=None, + help="Ignore any delta below this threshold", + ) args = parser.parse_args() if not sys.stdout.isatty(): - COLOR_RESET = '' - COLOR_RED = '' + COLOR_RESET = "" + COLOR_RED = "" yml = yaml.safe_load(open(args.path[0])) - device = yml['devices'][0] - absinfo = device['evdev']['absinfo'] + device = yml["devices"][0] + absinfo = device["evdev"]["absinfo"] try: nslots = absinfo[libevdev.EV_ABS.ABS_MT_SLOT.value][1] + 1 except KeyError: @@ -195,11 +223,15 @@ def main(argv): nskipped_lines = 0 - for event in device['events']: - for evdev in event['evdev']: + for event in device["events"]: + for evdev in event["evdev"]: s = slots[slot] - e = libevdev.InputEvent(code=libevdev.evbit(evdev[2], evdev[3]), - value=evdev[4], sec=evdev[0], usec=evdev[1]) + e = libevdev.InputEvent( + code=libevdev.evbit(evdev[2], evdev[3]), + value=evdev[4], + sec=evdev[0], + usec=evdev[1], + ) if e.code in tool_bits: tool_bits[e.code] = e.value @@ -208,8 +240,10 @@ def main(argv): # Note: this relies on the EV_KEY events to come in before the # x/y events, otherwise the last/first event in each slot will # be wrong. - if (e.code == libevdev.EV_KEY.BTN_TOOL_FINGER or - e.code == libevdev.EV_KEY.BTN_TOOL_PEN): + if ( + e.code == libevdev.EV_KEY.BTN_TOOL_FINGER + or e.code == libevdev.EV_KEY.BTN_TOOL_PEN + ): slot = 0 s = slots[slot] s.dirty = True @@ -251,7 +285,7 @@ def main(argv): s.dirty = True # bcm5974 cycles through slot numbers, so let's say all below # our current slot number was used - for sl in slots[:slot + 1]: + for sl in slots[: slot + 1]: sl.used = True elif e.code == libevdev.EV_ABS.ABS_MT_TRACKING_ID: if e.value == -1: @@ -290,11 +324,11 @@ def main(argv): last_time = t tools = [ - (libevdev.EV_KEY.BTN_TOOL_QUINTTAP, 'QIN'), - (libevdev.EV_KEY.BTN_TOOL_QUADTAP, 'QAD'), - (libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, 'TRI'), - (libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, 'DBL'), - (libevdev.EV_KEY.BTN_TOUCH, 'TOU'), + (libevdev.EV_KEY.BTN_TOOL_QUINTTAP, "QIN"), + (libevdev.EV_KEY.BTN_TOOL_QUADTAP, "QAD"), + (libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, "TRI"), + (libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, "DBL"), + (libevdev.EV_KEY.BTN_TOUCH, "TOU"), ] for bit, string in tools: @@ -302,12 +336,14 @@ def main(argv): tool_state = string break else: - tool_state = ' ' + tool_state = " " - fmt = SlotFormatter(is_absolute=args.use_absolute, - resolution=(xres, yres) if args.use_mm else None, - threshold=args.threshold, - ignore_below=args.ignore_below) + fmt = SlotFormatter( + is_absolute=args.use_absolute, + resolution=(xres, yres) if args.use_mm else None, + threshold=args.threshold, + ignore_below=args.ignore_below, + ) for sl in [s for s in slots if s.used]: fmt.format_slot(sl) @@ -323,11 +359,21 @@ def main(argv): if nskipped_lines > 0: print("") nskipped_lines = 0 - print("{:2d}.{:06d} {:+5d}ms {}: {}".format(e.sec, e.usec, tdelta, tool_state, fmt)) + print( + "{:2d}.{:06d} {:+5d}ms {}: {}".format( + e.sec, e.usec, tdelta, tool_state, fmt + ) + ) elif fmt.filtered: nskipped_lines += 1 - print("\r", " " * 21, "... {} below threshold".format(nskipped_lines), flush=True, end='') + print( + "\r", + " " * 21, + "... {} below threshold".format(nskipped_lines), + flush=True, + end="", + ) -if __name__ == '__main__': +if __name__ == "__main__": main(sys.argv) diff --git a/tools/libinput-measure-fuzz.py b/tools/libinput-measure-fuzz.py index 6cb9f893..223a4cdf 100755 --- a/tools/libinput-measure-fuzz.py +++ b/tools/libinput-measure-fuzz.py @@ -28,26 +28,29 @@ import os import sys import argparse import subprocess + try: import libevdev import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(str(e)), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(str(e)), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) -DEFAULT_HWDB_FILE = '/usr/lib/udev/hwdb.d/60-evdev.hwdb' -OVERRIDE_HWDB_FILE = '/etc/udev/hwdb.d/99-touchpad-fuzz-override.hwdb' +DEFAULT_HWDB_FILE = "/usr/lib/udev/hwdb.d/60-evdev.hwdb" +OVERRIDE_HWDB_FILE = "/etc/udev/hwdb.d/99-touchpad-fuzz-override.hwdb" class tcolors: - GREEN = '\033[92m' - RED = '\033[91m' - YELLOW = '\033[93m' - BOLD = '\033[1m' - NORMAL = '\033[0m' + GREEN = "\033[92m" + RED = "\033[91m" + YELLOW = "\033[93m" + BOLD = "\033[1m" + NORMAL = "\033[0m" def print_bold(msg, **kwargs): @@ -81,44 +84,53 @@ class Device(libevdev.Device): else: self.path = path - fd = open(self.path, 'rb') + fd = open(self.path, "rb") super().__init__(fd) context = pyudev.Context() self.udev_device = pyudev.Devices.from_device_file(context, self.path) def find_touch_device(self): context = pyudev.Context() - for device in context.list_devices(subsystem='input'): - if not device.get('ID_INPUT_TOUCHPAD', 0): + for device in context.list_devices(subsystem="input"): + if not device.get("ID_INPUT_TOUCHPAD", 0): continue - if not device.device_node or \ - not device.device_node.startswith('/dev/input/event'): + if not device.device_node or not device.device_node.startswith( + "/dev/input/event" + ): continue return device.device_node - print('Unable to find a touch device.', file=sys.stderr) + print("Unable to find a touch device.", file=sys.stderr) sys.exit(1) def check_property(self): - '''Return a tuple of (xfuzz, yfuzz) with the fuzz as set in the libinput - property. Returns None if the property doesn't exist''' + """Return a tuple of (xfuzz, yfuzz) with the fuzz as set in the libinput + property. Returns None if the property doesn't exist""" axes = { - 0x00: self.udev_device.get('LIBINPUT_FUZZ_00'), - 0x01: self.udev_device.get('LIBINPUT_FUZZ_01'), - 0x35: self.udev_device.get('LIBINPUT_FUZZ_35'), - 0x36: self.udev_device.get('LIBINPUT_FUZZ_36'), + 0x00: self.udev_device.get("LIBINPUT_FUZZ_00"), + 0x01: self.udev_device.get("LIBINPUT_FUZZ_01"), + 0x35: self.udev_device.get("LIBINPUT_FUZZ_35"), + 0x36: self.udev_device.get("LIBINPUT_FUZZ_36"), } if axes[0x35] is not None: if axes[0x35] != axes[0x00]: - print_bold('WARNING: fuzz mismatch ABS_X: {}, ABS_MT_POSITION_X: {}'.format(axes[0x00], axes[0x35])) + print_bold( + "WARNING: fuzz mismatch ABS_X: {}, ABS_MT_POSITION_X: {}".format( + axes[0x00], axes[0x35] + ) + ) if axes[0x36] is not None: if axes[0x36] != axes[0x01]: - print_bold('WARNING: fuzz mismatch ABS_Y: {}, ABS_MT_POSITION_Y: {}'.format(axes[0x01], axes[0x36])) + print_bold( + "WARNING: fuzz mismatch ABS_Y: {}, ABS_MT_POSITION_Y: {}".format( + axes[0x01], axes[0x36] + ) + ) xfuzz = axes[0x35] or axes[0x00] yfuzz = axes[0x36] or axes[0x01] @@ -126,27 +138,34 @@ class Device(libevdev.Device): if xfuzz is None and yfuzz is None: return None - if ((xfuzz is not None and yfuzz is None) or - (xfuzz is None and yfuzz is not None)): - raise InvalidConfigurationError('fuzz should be set for both axes') + if (xfuzz is not None and yfuzz is None) or ( + xfuzz is None and yfuzz is not None + ): + raise InvalidConfigurationError("fuzz should be set for both axes") return (int(xfuzz), int(yfuzz)) def check_axes(self): - ''' + """ Returns a tuple of (xfuzz, yfuzz) with the fuzz as set on the device axis. Returns None if no fuzz is set. - ''' + """ if not self.has(libevdev.EV_ABS.ABS_X) or not self.has(libevdev.EV_ABS.ABS_Y): - raise InvalidDeviceError('device does not have x/y axes') + raise InvalidDeviceError("device does not have x/y axes") - if self.has(libevdev.EV_ABS.ABS_MT_POSITION_X) != self.has(libevdev.EV_ABS.ABS_MT_POSITION_Y): - raise InvalidDeviceError('device does not have both multitouch axes') + if self.has(libevdev.EV_ABS.ABS_MT_POSITION_X) != self.has( + libevdev.EV_ABS.ABS_MT_POSITION_Y + ): + raise InvalidDeviceError("device does not have both multitouch axes") - xfuzz = (self.absinfo[libevdev.EV_ABS.ABS_X].fuzz or - self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X].fuzz) - yfuzz = (self.absinfo[libevdev.EV_ABS.ABS_Y].fuzz or - self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_Y].fuzz) + xfuzz = ( + self.absinfo[libevdev.EV_ABS.ABS_X].fuzz + or self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X].fuzz + ) + yfuzz = ( + self.absinfo[libevdev.EV_ABS.ABS_Y].fuzz + or self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_Y].fuzz + ) if xfuzz == 0 and yfuzz == 0: return None @@ -155,13 +174,13 @@ class Device(libevdev.Device): def print_fuzz(what, fuzz): - print(' Checking {}... '.format(what), end='') + print(" Checking {}... ".format(what), end="") if fuzz is None: - print('not set') + print("not set") elif fuzz == (0, 0): - print('is zero') + print("is zero") else: - print('x={} y={}'.format(*fuzz)) + print("x={} y={}".format(*fuzz)) def handle_existing_entry(device, fuzz): @@ -174,10 +193,10 @@ def handle_existing_entry(device, fuzz): # If the lines aren't in the same order in the file, it'll be a false # negative. overrides = { - 0x00: device.udev_device.get('EVDEV_ABS_00'), - 0x01: device.udev_device.get('EVDEV_ABS_01'), - 0x35: device.udev_device.get('EVDEV_ABS_35'), - 0x36: device.udev_device.get('EVDEV_ABS_36'), + 0x00: device.udev_device.get("EVDEV_ABS_00"), + 0x01: device.udev_device.get("EVDEV_ABS_01"), + 0x35: device.udev_device.get("EVDEV_ABS_35"), + 0x36: device.udev_device.get("EVDEV_ABS_36"), } has_existing_rules = False @@ -188,85 +207,98 @@ def handle_existing_entry(device, fuzz): if not has_existing_rules: return False - print_red('Error! ', end='') - print('This device already has axis overrides defined') - print('') - print_bold('Searching for existing override...') + print_red("Error! ", end="") + print("This device already has axis overrides defined") + print("") + print_bold("Searching for existing override...") # Construct a template that looks like a hwdb entry (values only) from # the udev property values - template = [' EVDEV_ABS_00={}'.format(overrides[0x00]), - ' EVDEV_ABS_01={}'.format(overrides[0x01])] + template = [ + " EVDEV_ABS_00={}".format(overrides[0x00]), + " EVDEV_ABS_01={}".format(overrides[0x01]), + ] if overrides[0x35] is not None: - template += [' EVDEV_ABS_35={}'.format(overrides[0x35]), - ' EVDEV_ABS_36={}'.format(overrides[0x36])] + template += [ + " EVDEV_ABS_35={}".format(overrides[0x35]), + " EVDEV_ABS_36={}".format(overrides[0x36]), + ] - print('Checking in {}... '.format(OVERRIDE_HWDB_FILE), end='') + print("Checking in {}... ".format(OVERRIDE_HWDB_FILE), end="") entry, prefix, lineno = check_file_for_lines(OVERRIDE_HWDB_FILE, template) if entry is not None: - print_green('found') - print('The existing hwdb entry can be overwritten') + print_green("found") + print("The existing hwdb entry can be overwritten") return False else: - print_red('not found') - print('Checking in {}... '.format(DEFAULT_HWDB_FILE), end='') + print_red("not found") + print("Checking in {}... ".format(DEFAULT_HWDB_FILE), end="") entry, prefix, lineno = check_file_for_lines(DEFAULT_HWDB_FILE, template) if entry is not None: - print_green('found') + print_green("found") else: - print_red('not found') - print('The device has a hwdb override defined but it\'s not where I expected it to be.') - print('Please look at the libinput documentation for more details.') - print('Exiting now.') + print_red("not found") + print( + "The device has a hwdb override defined but it's not where I expected it to be." + ) + print("Please look at the libinput documentation for more details.") + print("Exiting now.") return True - print_bold('Probable entry for this device found in line {}:'.format(lineno)) - print('\n'.join(prefix + entry)) - print('') + print_bold("Probable entry for this device found in line {}:".format(lineno)) + print("\n".join(prefix + entry)) + print("") - print_bold('Suggested new entry for this device:') + print_bold("Suggested new entry for this device:") new_entry = [] for i in range(0, len(template)): - parts = entry[i].split(':') + parts = entry[i].split(":") while len(parts) < 4: - parts.append('') + parts.append("") parts[3] = str(fuzz) - new_entry.append(':'.join(parts)) - print('\n'.join(prefix + new_entry)) - print('') + new_entry.append(":".join(parts)) + print("\n".join(prefix + new_entry)) + print("") # Not going to overwrite the 60-evdev.hwdb entry with this program, too # risky. And it may not be our device match anyway. - print_bold('You must now:') - print('\n'.join(( - '1. Check the above suggestion for sanity. Does it match your device?', - '2. Open {} and amend the existing entry'.format(DEFAULT_HWDB_FILE), - ' as recommended above', - '', - ' The property format is:', - ' EVDEV_ABS_00=min:max:resolution:fuzz', - '', - ' Leave the entry as-is and only add or amend the fuzz value.', - ' A non-existent value can be skipped, e.g. this entry sets the ', - ' resolution to 32 and the fuzz to 8', - ' EVDEV_ABS_00=::32:8', - '', - '3. Save the edited file', - '4. Say Y to the next prompt'))) + print_bold("You must now:") + print( + "\n".join( + ( + "1. Check the above suggestion for sanity. Does it match your device?", + "2. Open {} and amend the existing entry".format(DEFAULT_HWDB_FILE), + " as recommended above", + "", + " The property format is:", + " EVDEV_ABS_00=min:max:resolution:fuzz", + "", + " Leave the entry as-is and only add or amend the fuzz value.", + " A non-existent value can be skipped, e.g. this entry sets the ", + " resolution to 32 and the fuzz to 8", + " EVDEV_ABS_00=::32:8", + "", + "3. Save the edited file", + "4. Say Y to the next prompt", + ) + ) + ) - cont = input('Continue? [Y/n] ') - if cont == 'n': + cont = input("Continue? [Y/n] ") + if cont == "n": raise KeyboardInterrupt if test_hwdb_entry(device, fuzz): - print_bold('Please test the new fuzz setting by restarting libinput') - print_bold('Then submit a pull request for this hwdb entry change to ' - 'to systemd at http://github.com/systemd/systemd') + print_bold("Please test the new fuzz setting by restarting libinput") + print_bold( + "Then submit a pull request for this hwdb entry change to " + "to systemd at http://github.com/systemd/systemd" + ) else: - print_bold('The new fuzz setting did not take effect.') - print_bold('Did you edit the correct file?') - print('Please look at the libinput documentation for more details.') - print('Exiting now.') + print_bold("The new fuzz setting did not take effect.") + print_bold("Did you edit the correct file?") + print("Please look at the libinput documentation for more details.") + print("Exiting now.") return True @@ -274,47 +306,49 @@ def handle_existing_entry(device, fuzz): def reload_and_trigger_udev(device): import time - print('Running systemd-hwdb update') - subprocess.run(['systemd-hwdb', 'update'], check=True) - syspath = device.path.replace('/dev/input/', '/sys/class/input/') + print("Running systemd-hwdb update") + subprocess.run(["systemd-hwdb", "update"], check=True) + syspath = device.path.replace("/dev/input/", "/sys/class/input/") time.sleep(2) - print('Running udevadm trigger {}'.format(syspath)) - subprocess.run(['udevadm', 'trigger', syspath], check=True) + print("Running udevadm trigger {}".format(syspath)) + subprocess.run(["udevadm", "trigger", syspath], check=True) time.sleep(2) def test_hwdb_entry(device, fuzz): reload_and_trigger_udev(device) - print_bold('Testing... ', end='') + print_bold("Testing... ", end="") d = Device(device.path) f = d.check_axes() if f is not None: if f == (fuzz, fuzz): - print_yellow('Warning') - print_bold('The hwdb applied to the device but libinput\'s udev ' - 'rules have not picked it up. This should only happen' - 'if libinput is not installed') + print_yellow("Warning") + print_bold( + "The hwdb applied to the device but libinput's udev " + "rules have not picked it up. This should only happen" + "if libinput is not installed" + ) return True else: - print_red('Error') + print_red("Error") return False else: f = d.check_property() if f is not None and f == (fuzz, fuzz): - print_green('Success') + print_green("Success") return True else: - print_red('Error') + print_red("Error") return False def check_file_for_lines(path, template): - ''' + """ Checks file at path for the lines given in template. If found, the return value is a tuple of the matching lines and the prefix (i.e. the two lines before the matching lines) - ''' + """ try: lines = [l[:-1] for l in open(path).readlines()] idx = -1 @@ -322,12 +356,12 @@ def check_file_for_lines(path, template): while idx < len(lines) - 1: idx += 1 line = lines[idx] - if not line.startswith(' EVDEV_ABS_00'): + if not line.startswith(" EVDEV_ABS_00"): continue - if lines[idx:idx + len(template)] != template: + if lines[idx : idx + len(template)] != template: continue - return (lines[idx:idx + len(template)], lines[idx - 2:idx], idx) + return (lines[idx : idx + len(template)], lines[idx - 2 : idx], idx) except IndexError: pass @@ -338,43 +372,51 @@ def check_file_for_lines(path, template): def write_udev_rule(device, fuzz): - '''Write out a udev rule that may match the device, run udevadm trigger and + """Write out a udev rule that may match the device, run udevadm trigger and check if the udev rule worked. Of course, there's plenty to go wrong... - ''' - print('') - print_bold('Guessing a udev rule to overwrite the fuzz') + """ + print("") + print_bold("Guessing a udev rule to overwrite the fuzz") # Some devices match better on pvr, others on pn, so we get to try both. yay - modalias = open('/sys/class/dmi/id/modalias').readlines()[0] - ms = modalias.split(':') + modalias = open("/sys/class/dmi/id/modalias").readlines()[0] + ms = modalias.split(":") svn, pn, pvr = None, None, None for m in ms: - if m.startswith('svn'): + if m.startswith("svn"): svn = m - elif m.startswith('pn'): + elif m.startswith("pn"): pn = m - elif m.startswith('pvr'): + elif m.startswith("pvr"): pvr = m # Let's print out both to inform and/or confuse the user - template = '\n'.join(('# {} {}', - 'evdev:name:{}:dmi:*:{}*:{}*:', - ' EVDEV_ABS_00=:::{}', - ' EVDEV_ABS_01=:::{}', - ' EVDEV_ABS_35=:::{}', - ' EVDEV_ABS_36=:::{}', - '')) - rule1 = template.format(svn[3:], device.name, device.name, svn, pvr, fuzz, fuzz, fuzz, fuzz) - rule2 = template.format(svn[3:], device.name, device.name, svn, pn, fuzz, fuzz, fuzz, fuzz) + template = "\n".join( + ( + "# {} {}", + "evdev:name:{}:dmi:*:{}*:{}*:", + " EVDEV_ABS_00=:::{}", + " EVDEV_ABS_01=:::{}", + " EVDEV_ABS_35=:::{}", + " EVDEV_ABS_36=:::{}", + "", + ) + ) + rule1 = template.format( + svn[3:], device.name, device.name, svn, pvr, fuzz, fuzz, fuzz, fuzz + ) + rule2 = template.format( + svn[3:], device.name, device.name, svn, pn, fuzz, fuzz, fuzz, fuzz + ) - print('Full modalias is: {}'.format(modalias)) + print("Full modalias is: {}".format(modalias)) print() - print_bold('Suggested udev rule, option 1:') + print_bold("Suggested udev rule, option 1:") print(rule1) print() - print_bold('Suggested udev rule, option 2:') + print_bold("Suggested udev rule, option 2:") print(rule2) - print('') + print("") # The weird hwdb matching behavior means we match on the least specific # rule (i.e. most wildcards) first although that was supposed to be fixed in @@ -386,77 +428,88 @@ def write_udev_rule(device, fuzz): return while True: - print_bold('Wich rule do you want to to test? 1 or 2? ', end='') - yesno = input('Ctrl+C to exit ') + print_bold("Wich rule do you want to to test? 1 or 2? ", end="") + yesno = input("Ctrl+C to exit ") - if yesno == '1': + if yesno == "1": rule = rule1 break - elif yesno == '2': + elif yesno == "2": rule = rule2 break fname = OVERRIDE_HWDB_FILE try: - fd = open(fname, 'x') + fd = open(fname, "x") except FileExistsError: - yesno = input('File {} exists, overwrite? [Y/n] '.format(fname)) - if yesno.lower == 'n': + yesno = input("File {} exists, overwrite? [Y/n] ".format(fname)) + if yesno.lower == "n": return - fd = open(fname, 'w') + fd = open(fname, "w") - fd.write('# File generated by libinput measure fuzz\n\n') + fd.write("# File generated by libinput measure fuzz\n\n") fd.write(rule) fd.close() if test_hwdb_entry(device, fuzz): - print('Your hwdb override file is in {}'.format(fname)) - print_bold('Please test the new fuzz setting by restarting libinput') - print_bold('Then submit a pull request for this hwdb entry to ' - 'systemd at http://github.com/systemd/systemd') + print("Your hwdb override file is in {}".format(fname)) + print_bold("Please test the new fuzz setting by restarting libinput") + print_bold( + "Then submit a pull request for this hwdb entry to " + "systemd at http://github.com/systemd/systemd" + ) else: - print('The hwdb entry failed to apply to the device.') - print('Removing hwdb file again.') + print("The hwdb entry failed to apply to the device.") + print("Removing hwdb file again.") os.remove(fname) reload_and_trigger_udev(device) - print_bold('What now?') - print('1. Re-run this program and try the other suggested udev rule. If that fails,') - print('2. File a bug with the suggested udev rule at http://github.com/systemd/systemd') + print_bold("What now?") + print( + "1. Re-run this program and try the other suggested udev rule. If that fails," + ) + print( + "2. File a bug with the suggested udev rule at http://github.com/systemd/systemd" + ) def main(args): parser = argparse.ArgumentParser( - description='Print fuzz settings and/or suggest udev rules for the fuzz to be adjusted.' + description="Print fuzz settings and/or suggest udev rules for the fuzz to be adjusted." ) - parser.add_argument('path', metavar='/dev/input/event0', - nargs='?', type=str, help='Path to device (optional)') - parser.add_argument('--fuzz', type=int, help='Suggested fuzz') + parser.add_argument( + "path", + metavar="/dev/input/event0", + nargs="?", + type=str, + help="Path to device (optional)", + ) + parser.add_argument("--fuzz", type=int, help="Suggested fuzz") args = parser.parse_args() try: device = Device(args.path) - print_bold('Using {}: {}'.format(device.name, device.path)) + print_bold("Using {}: {}".format(device.name, device.path)) fuzz = device.check_property() - print_fuzz('udev property', fuzz) + print_fuzz("udev property", fuzz) fuzz = device.check_axes() - print_fuzz('axes', fuzz) + print_fuzz("axes", fuzz) userfuzz = args.fuzz if userfuzz is not None: write_udev_rule(device, userfuzz) except PermissionError: - print('Permission denied, please re-run as root') + print("Permission denied, please re-run as root") except InvalidConfigurationError as e: - print('Error: {}'.format(e)) + print("Error: {}".format(e)) except InvalidDeviceError as e: - print('Error: {}'.format(e)) + print("Error: {}".format(e)) except KeyboardInterrupt: - print('Exited on user request') + print("Exited on user request") -if __name__ == '__main__': +if __name__ == "__main__": main(sys.argv) diff --git a/tools/libinput-measure-touch-size.py b/tools/libinput-measure-touch-size.py index 5d98bc28..43f14cda 100755 --- a/tools/libinput-measure-touch-size.py +++ b/tools/libinput-measure-touch-size.py @@ -27,21 +27,25 @@ import sys import subprocess import argparse + try: import libevdev import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(str(e)), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(str(e)), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) class Range(object): """Class to keep a min/max of a value around""" + def __init__(self): - self.min = float('inf') - self.max = float('-inf') + self.min = float("inf") + self.max = float("-inf") def update(self, value): self.min = min(self.min, value) @@ -148,7 +152,9 @@ class TouchSequence(object): self.major_range.min, self.major_range.max ) if self.device.has_minor: - s += "minor: [{:3d}..{:3d}] ".format(self.minor_range.min, self.minor_range.max) + s += "minor: [{:3d}..{:3d}] ".format( + self.minor_range.min, self.minor_range.max + ) if self.was_down: s += " down" if self.was_palm: @@ -160,10 +166,12 @@ class TouchSequence(object): def _str_state(self): touch = self.points[-1] - s = "{}, tags: {} {} {}".format(touch, - "down" if self.is_down else " ", - "palm" if self.is_palm else " ", - "thumb" if self.is_thumb else " ") + s = "{}, tags: {} {} {}".format( + touch, + "down" if self.is_down else " ", + "palm" if self.is_palm else " ", + "thumb" if self.is_thumb else " ", + ) return s @@ -178,7 +186,7 @@ class Device(libevdev.Device): else: self.path = path - fd = open(self.path, 'rb') + fd = open(self.path, "rb") super().__init__(fd) print("Using {}: {}\n".format(self.name, self.path)) @@ -200,13 +208,15 @@ class Device(libevdev.Device): def find_touch_device(self): context = pyudev.Context() - for device in context.list_devices(subsystem='input'): - if not device.get('ID_INPUT_TOUCHPAD', 0) and \ - not device.get('ID_INPUT_TOUCHSCREEN', 0): + for device in context.list_devices(subsystem="input"): + if not device.get("ID_INPUT_TOUCHPAD", 0) and not device.get( + "ID_INPUT_TOUCHSCREEN", 0 + ): continue - if not device.device_node or \ - not device.device_node.startswith('/dev/input/event'): + if not device.device_node or not device.device_node.startswith( + "/dev/input/event" + ): continue return device.device_node @@ -215,21 +225,24 @@ class Device(libevdev.Device): sys.exit(1) def _init_thresholds_from_quirks(self): - command = ['libinput', 'quirks', 'list', self.path] + command = ["libinput", "quirks", "list", self.path] cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if cmd.returncode != 0: - print("Error querying quirks: {}".format(cmd.stderr.decode('utf-8')), file=sys.stderr) + print( + "Error querying quirks: {}".format(cmd.stderr.decode("utf-8")), + file=sys.stderr, + ) return - stdout = cmd.stdout.decode('utf-8') - quirks = [q.split('=') for q in stdout.split('\n')] + stdout = cmd.stdout.decode("utf-8") + quirks = [q.split("=") for q in stdout.split("\n")] for q in quirks: - if q[0] == 'AttrPalmSizeThreshold': + if q[0] == "AttrPalmSizeThreshold": self.palm = int(q[1]) - elif q[0] == 'AttrTouchSizeRange': + elif q[0] == "AttrTouchSizeRange": self.down, self.up = colon_tuple(q[1]) - elif q[0] == 'AttrThumbSizeThreshold': + elif q[0] == "AttrThumbSizeThreshold": self.thumb = int(q[1]) def start_new_sequence(self, tracking_id): @@ -239,13 +252,17 @@ class Device(libevdev.Device): return self.sequences[-1] def handle_key(self, event): - tapcodes = [libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, - libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, - libevdev.EV_KEY.BTN_TOOL_QUADTAP, - libevdev.EV_KEY.BTN_TOOL_QUINTTAP] + tapcodes = [ + libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, + libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, + libevdev.EV_KEY.BTN_TOOL_QUADTAP, + libevdev.EV_KEY.BTN_TOOL_QUINTTAP, + ] if event.code in tapcodes and event.value > 0: - print("\rThis tool cannot handle multiple fingers, " - "output will be invalid", file=sys.stderr) + print( + "\rThis tool cannot handle multiple fingers, " "output will be invalid", + file=sys.stderr, + ) def handle_abs(self, event): if event.matches(libevdev.EV_ABS.ABS_MT_TRACKING_ID): @@ -271,9 +288,11 @@ class Device(libevdev.Device): try: self.current_sequence().append(self.touch) print("\r{}".format(self.current_sequence()), end="") - self.touch = Touch(major=self.touch.major, - minor=self.touch.minor, - orientation=self.touch.orientation) + self.touch = Touch( + major=self.touch.major, + minor=self.touch.minor, + orientation=self.touch.orientation, + ) except IndexError: pass @@ -290,8 +309,10 @@ class Device(libevdev.Device): print("Touch sizes used: {}:{}".format(self.down, self.up)) print("Palm size used: {}".format(self.palm)) print("Thumb size used: {}".format(self.thumb)) - print("Place a single finger on the device to measure touch size.\n" - "Ctrl+C to exit\n") + print( + "Place a single finger on the device to measure touch size.\n" + "Ctrl+C to exit\n" + ) while True: for event in self.events(): @@ -300,11 +321,11 @@ class Device(libevdev.Device): def colon_tuple(string): try: - ts = string.split(':') + ts = string.split(":") t = tuple([int(x) for x in ts]) if len(t) == 2 and t[0] >= t[1]: return t - except: # noqa + except: # noqa pass msg = "{} is not in format N:M (N >= M)".format(string) @@ -313,13 +334,25 @@ def colon_tuple(string): def main(args): parser = argparse.ArgumentParser(description="Measure touch size and orientation") - parser.add_argument('path', metavar='/dev/input/event0', - nargs='?', type=str, help='Path to device (optional)') - parser.add_argument('--touch-thresholds', metavar='down:up', - type=colon_tuple, - help='Thresholds when a touch is logically down or up') - parser.add_argument('--palm-threshold', metavar='t', - type=int, help='Threshold when a touch is a palm') + parser.add_argument( + "path", + metavar="/dev/input/event0", + nargs="?", + type=str, + help="Path to device (optional)", + ) + parser.add_argument( + "--touch-thresholds", + metavar="down:up", + type=colon_tuple, + help="Thresholds when a touch is logically down or up", + ) + parser.add_argument( + "--palm-threshold", + metavar="t", + type=int, + help="Threshold when a touch is a palm", + ) args = parser.parse_args() try: @@ -337,7 +370,9 @@ def main(args): except (PermissionError, OSError): print("Error: failed to open device") except InvalidDeviceError as e: - print("This device does not have the capabilities for size-based touch detection.") + print( + "This device does not have the capabilities for size-based touch detection." + ) print("Details: {}".format(e)) diff --git a/tools/libinput-measure-touchpad-pressure.py b/tools/libinput-measure-touchpad-pressure.py index a55bad0c..24ab69e0 100755 --- a/tools/libinput-measure-touchpad-pressure.py +++ b/tools/libinput-measure-touchpad-pressure.py @@ -27,13 +27,16 @@ import sys import subprocess import argparse + try: import libevdev import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(str(e)), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(str(e)), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) @@ -48,35 +51,35 @@ class TableFormatter(object): return sum(self.colwidths) + 1 def headers(self, args): - s = '|' + s = "|" align = self.ALIGNMENT - 1 # account for | for arg in args: # +2 because we want space left/right of text w = ((len(arg) + 2 + align) // align) * align self.colwidths.append(w + 1) - s += ' {:^{width}s} |'.format(arg, width=w - 2) + s += " {:^{width}s} |".format(arg, width=w - 2) return s def values(self, args): - s = '|' + s = "|" for w, arg in zip(self.colwidths, args): w -= 1 # width includes | separator if type(arg) == str: # We want space margins for strings - s += ' {:{width}s} |'.format(arg, width=w - 2) + s += " {:{width}s} |".format(arg, width=w - 2) elif type(arg) == bool: - s += '{:^{width}s}|'.format('x' if arg else ' ', width=w) + s += "{:^{width}s}|".format("x" if arg else " ", width=w) else: - s += '{:^{width}d}|'.format(arg, width=w) + s += "{:^{width}d}|".format(arg, width=w) if len(args) < len(self.colwidths): - s += '|'.rjust(self.width - len(s), ' ') + s += "|".rjust(self.width - len(s), " ") return s def separator(self): - return '+' + '-' * (self.width - 2) + '+' + return "+" + "-" * (self.width - 2) + "+" fmt = TableFormatter() @@ -84,9 +87,10 @@ fmt = TableFormatter() class Range(object): """Class to keep a min/max of a value around""" + def __init__(self): - self.min = float('inf') - self.max = float('-inf') + self.min = float("inf") + self.max = float("-inf") def update(self, value): self.min = min(self.min, value) @@ -157,19 +161,47 @@ class TouchSequence(object): def _str_summary(self): if not self.points: - return fmt.values([self.tracking_id, False, False, False, False, - 'No pressure values recorded']) + return fmt.values( + [ + self.tracking_id, + False, + False, + False, + False, + "No pressure values recorded", + ] + ) - s = fmt.values([self.tracking_id, self.was_down, True, self.was_palm, - self.was_thumb, self.prange.min, self.prange.max, 0, - self.avg(), self.median()]) + s = fmt.values( + [ + self.tracking_id, + self.was_down, + True, + self.was_palm, + self.was_thumb, + self.prange.min, + self.prange.max, + 0, + self.avg(), + self.median(), + ] + ) return s def _str_state(self): - s = fmt.values([self.tracking_id, self.is_down, not self.is_down, - self.is_palm, self.is_thumb, self.prange.min, - self.prange.max, self.points[-1].pressure]) + s = fmt.values( + [ + self.tracking_id, + self.is_down, + not self.is_down, + self.is_palm, + self.is_thumb, + self.prange.min, + self.prange.max, + self.points[-1].pressure, + ] + ) return s @@ -184,7 +216,7 @@ class Device(libevdev.Device): else: self.path = path - fd = open(self.path, 'rb') + fd = open(self.path, "rb") super().__init__(fd) print("Using {}: {}\n".format(self.name, self.path)) @@ -195,7 +227,9 @@ class Device(libevdev.Device): absinfo = self.absinfo[libevdev.EV_ABS.ABS_PRESSURE] self.has_mt_pressure = False if absinfo is None: - raise InvalidDeviceError("Device does not have ABS_PRESSURE or ABS_MT_PRESSURE") + raise InvalidDeviceError( + "Device does not have ABS_PRESSURE or ABS_MT_PRESSURE" + ) prange = absinfo.maximum - absinfo.minimum @@ -210,12 +244,13 @@ class Device(libevdev.Device): def find_touchpad_device(self): context = pyudev.Context() - for device in context.list_devices(subsystem='input'): - if not device.get('ID_INPUT_TOUCHPAD', 0): + for device in context.list_devices(subsystem="input"): + if not device.get("ID_INPUT_TOUCHPAD", 0): continue - if not device.device_node or \ - not device.device_node.startswith('/dev/input/event'): + if not device.device_node or not device.device_node.startswith( + "/dev/input/event" + ): continue return device.device_node @@ -223,21 +258,24 @@ class Device(libevdev.Device): sys.exit(1) def _init_thresholds_from_quirks(self): - command = ['libinput', 'quirks', 'list', self.path] + command = ["libinput", "quirks", "list", self.path] cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if cmd.returncode != 0: - print("Error querying quirks: {}".format(cmd.stderr.decode('utf-8')), file=sys.stderr) + print( + "Error querying quirks: {}".format(cmd.stderr.decode("utf-8")), + file=sys.stderr, + ) return - stdout = cmd.stdout.decode('utf-8') - quirks = [q.split('=') for q in stdout.split('\n')] + stdout = cmd.stdout.decode("utf-8") + quirks = [q.split("=") for q in stdout.split("\n")] for q in quirks: - if q[0] == 'AttrPalmPressureThreshold': + if q[0] == "AttrPalmPressureThreshold": self.palm = int(q[1]) - elif q[0] == 'AttrPressureRange': + elif q[0] == "AttrPressureRange": self.down, self.up = colon_tuple(q[1]) - elif q[0] == 'AttrThumbPressureThreshold': + elif q[0] == "AttrThumbPressureThreshold": self.thumb = int(q[1]) def start_new_sequence(self, tracking_id): @@ -252,11 +290,13 @@ def handle_key(device, event): libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, libevdev.EV_KEY.BTN_TOOL_QUADTAP, - libevdev.EV_KEY.BTN_TOOL_QUINTTAP + libevdev.EV_KEY.BTN_TOOL_QUINTTAP, ] if event.code in tapcodes and event.value > 0: - print('\r\033[2KThis tool cannot handle multiple fingers, ' - 'output will be invalid') + print( + "\r\033[2KThis tool cannot handle multiple fingers, " + "output will be invalid" + ) def handle_abs(device, event): @@ -271,8 +311,9 @@ def handle_abs(device, event): except IndexError: # If the finger was down at startup pass - elif (event.matches(libevdev.EV_ABS.ABS_MT_PRESSURE) or - (event.matches(libevdev.EV_ABS.ABS_PRESSURE) and not device.has_mt_pressure)): + elif event.matches(libevdev.EV_ABS.ABS_MT_PRESSURE) or ( + event.matches(libevdev.EV_ABS.ABS_PRESSURE) and not device.has_mt_pressure + ): try: s = device.current_sequence() s.append(Touch(pressure=event.value)) @@ -290,24 +331,26 @@ def handle_event(device, event): def loop(device): - print('This is an interactive tool') + print("This is an interactive tool") print() print("Place a single finger on the touchpad to measure pressure values.") - print('Check that:') - print('- touches subjectively perceived as down are tagged as down') - print('- touches with a thumb are tagged as thumb') - print('- touches with a palm are tagged as palm') + print("Check that:") + print("- touches subjectively perceived as down are tagged as down") + print("- touches with a thumb are tagged as thumb") + print("- touches with a palm are tagged as palm") print() - print('If the touch states do not match the interaction, re-run') - print('with --touch-thresholds=down:up using observed pressure values.') - print('See --help for more options.') + print("If the touch states do not match the interaction, re-run") + print("with --touch-thresholds=down:up using observed pressure values.") + print("See --help for more options.") print() print("Press Ctrl+C to exit") print() - headers = fmt.headers(['Touch', 'down', 'up', 'palm', 'thumb', 'min', 'max', 'p', 'avg', 'median']) + headers = fmt.headers( + ["Touch", "down", "up", "palm", "thumb", "min", "max", "p", "avg", "median"] + ) print(fmt.separator()) - print(fmt.values(['Thresh', device.down, device.up, device.palm, device.thumb])) + print(fmt.values(["Thresh", device.down, device.up, device.palm, device.thumb])) print(fmt.separator()) print(headers) print(fmt.separator()) @@ -319,11 +362,11 @@ def loop(device): def colon_tuple(string): try: - ts = string.split(':') + ts = string.split(":") t = tuple([int(x) for x in ts]) if len(t) == 2 and t[0] >= t[1]: return t - except: # noqa + except: # noqa pass msg = "{} is not in format N:M (N >= M)".format(string) @@ -331,24 +374,31 @@ def colon_tuple(string): def main(args): - parser = argparse.ArgumentParser( - description="Measure touchpad pressure values" + parser = argparse.ArgumentParser(description="Measure touchpad pressure values") + parser.add_argument( + "path", + metavar="/dev/input/event0", + nargs="?", + type=str, + help="Path to device (optional)", ) parser.add_argument( - 'path', metavar='/dev/input/event0', nargs='?', type=str, - help='Path to device (optional)' + "--touch-thresholds", + metavar="down:up", + type=colon_tuple, + help="Thresholds when a touch is logically down or up", ) parser.add_argument( - '--touch-thresholds', metavar='down:up', type=colon_tuple, - help='Thresholds when a touch is logically down or up' + "--palm-threshold", + metavar="t", + type=int, + help="Threshold when a touch is a palm", ) parser.add_argument( - '--palm-threshold', metavar='t', type=int, - help='Threshold when a touch is a palm' - ) - parser.add_argument( - '--thumb-threshold', metavar='t', type=int, - help='Threshold when a touch is a thumb' + "--thumb-threshold", + metavar="t", + type=int, + help="Threshold when a touch is a thumb", ) args = parser.parse_args() @@ -366,13 +416,15 @@ def main(args): loop(device) except KeyboardInterrupt: - print('\r\033[2K{}'.format(fmt.separator())) + print("\r\033[2K{}".format(fmt.separator())) print() except (PermissionError, OSError): print("Error: failed to open device") except InvalidDeviceError as e: - print("This device does not have the capabilities for pressure-based touch detection.") + print( + "This device does not have the capabilities for pressure-based touch detection." + ) print("Details: {}".format(e)) diff --git a/tools/libinput-measure-touchpad-size.py b/tools/libinput-measure-touchpad-size.py index 27c618c4..2c10953e 100755 --- a/tools/libinput-measure-touchpad-size.py +++ b/tools/libinput-measure-touchpad-size.py @@ -26,13 +26,16 @@ import sys import argparse + try: import libevdev import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(str(e)), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(str(e)), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) @@ -51,15 +54,15 @@ class Touchpad(object): x = evdev.absinfo[libevdev.EV_ABS.ABS_X] y = evdev.absinfo[libevdev.EV_ABS.ABS_Y] if not x or not y: - raise DeviceError('Device does not have an x or axis') + raise DeviceError("Device does not have an x or axis") if not x.resolution or not y.resolution: - print('Device does not have resolutions.', file=sys.stderr) + print("Device does not have resolutions.", file=sys.stderr) x.resolution = 1 y.resolution = 1 - self.xrange = (x.maximum - x.minimum) - self.yrange = (y.maximum - y.minimum) + self.xrange = x.maximum - x.minimum + self.yrange = y.maximum - y.minimum self.width = self.xrange / x.resolution self.height = self.yrange / y.resolution @@ -70,7 +73,12 @@ class Touchpad(object): # terminal character space is (guesswork) ca 2.3 times as high as # wide. self.columns = 30 - self.rows = int(self.columns * (self.yrange // y.resolution) // (self.xrange // x.resolution) / 2.3) + self.rows = int( + self.columns + * (self.yrange // y.resolution) + // (self.xrange // x.resolution) + / 2.3 + ) self.pos = Point(0, 0) self.min = Point() self.max = Point() @@ -87,8 +95,8 @@ class Touchpad(object): def x(self, x): self._x.minimum = min(self.x.minimum, x) self._x.maximum = max(self.x.maximum, x) - self.min.x = min(x, self.min.x or 0xffffffff) - self.max.x = max(x, self.max.x or -0xffffffff) + self.min.x = min(x, self.min.x or 0xFFFFFFFF) + self.max.x = max(x, self.max.x or -0xFFFFFFFF) # we calculate the position based on the original range. # this means on devices with a narrower range than advertised, not # all corners may be reachable in the touchpad drawing. @@ -98,8 +106,8 @@ class Touchpad(object): def y(self, y): self._y.minimum = min(self.y.minimum, y) self._y.maximum = max(self.y.maximum, y) - self.min.y = min(y, self.min.y or 0xffffffff) - self.max.y = max(y, self.max.y or -0xffffffff) + self.min.y = min(y, self.min.y or 0xFFFFFFFF) + self.max.y = max(y, self.max.y or -0xFFFFFFFF) # we calculate the position based on the original range. # this means on devices with a narrower range than advertised, not # all corners may be reachable in the touchpad drawing. @@ -107,61 +115,61 @@ class Touchpad(object): def update_from_data(self): if None in [self.min.x, self.min.y, self.max.x, self.max.y]: - raise DeviceError('Insufficient data to continue') + raise DeviceError("Insufficient data to continue") self._x.minimum = self.min.x self._x.maximum = self.max.x self._y.minimum = self.min.y self._y.maximum = self.max.y def draw(self): - print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( - self.min.x if self.min.x is not None else 0, - self.max.x if self.max.x is not None else 0, - self.min.y if self.min.y is not None else 0, - self.max.y if self.max.y is not None else 0)) + print( + "Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format( + self.min.x if self.min.x is not None else 0, + self.max.x if self.max.x is not None else 0, + self.min.y if self.min.y is not None else 0, + self.max.y if self.max.y is not None else 0, + ) + ) print() - print('Move one finger along all edges of the touchpad'.center(self.columns)) - print('until the detected axis range stops changing.'.center(self.columns)) + print("Move one finger along all edges of the touchpad".center(self.columns)) + print("until the detected axis range stops changing.".center(self.columns)) top = int(self.pos.y * self.rows) - print('+{}+'.format(''.ljust(self.columns, '-'))) + print("+{}+".format("".ljust(self.columns, "-"))) for row in range(0, top): - print('|{}|'.format(''.ljust(self.columns))) + print("|{}|".format("".ljust(self.columns))) left = int(self.pos.x * self.columns) right = max(0, self.columns - 1 - left) - print('|{}{}{}|'.format( - ''.ljust(left), - 'O', - ''.ljust(right))) + print("|{}{}{}|".format("".ljust(left), "O", "".ljust(right))) for row in range(top + 1, self.rows): - print('|{}|'.format(''.ljust(self.columns))) + print("|{}|".format("".ljust(self.columns))) - print('+{}+'.format(''.ljust(self.columns, '-'))) + print("+{}+".format("".ljust(self.columns, "-"))) - print('Press Ctrl+C to stop'.center(self.columns)) + print("Press Ctrl+C to stop".center(self.columns)) - print('\033[{}A'.format(self.rows + 8), flush=True) + print("\033[{}A".format(self.rows + 8), flush=True) self.rows_printed = self.rows + 8 def erase(self): # Erase all previous lines so we're not left with rubbish for row in range(self.rows_printed): - print('\033[K') - print('\033[{}A'.format(self.rows_printed)) + print("\033[K") + print("\033[{}A".format(self.rows_printed)) def dimension(string): try: - ts = string.split('x') + ts = string.split("x") t = tuple([int(x) for x in ts]) if len(t) == 2: return t - except: # noqa + except: # noqa pass msg = "{} is not in format WxH".format(string) @@ -173,82 +181,95 @@ def between(v1, v2, deviation): def dmi_modalias_match(modalias): - modalias = modalias.split(':') - dmi = {'svn': None, 'pvr': None, 'pn': None} + modalias = modalias.split(":") + dmi = {"svn": None, "pvr": None, "pn": None} for m in modalias: for key in dmi: if m.startswith(key): - dmi[key] = m[len(key):] + dmi[key] = m[len(key) :] # Based on the current 60-evdev.hwdb, Lenovo uses pvr and everyone else # uses pn to provide a human-identifiable match - if dmi['svn'] == 'LENOVO': - return 'dmi:*svn{}:*pvr{}*'.format(dmi['svn'], dmi['pvr']) + if dmi["svn"] == "LENOVO": + return "dmi:*svn{}:*pvr{}*".format(dmi["svn"], dmi["pvr"]) else: - return 'dmi:*svn{}:*pn{}*'.format(dmi['svn'], dmi['pn']) + return "dmi:*svn{}:*pn{}*".format(dmi["svn"], dmi["pn"]) def main(args): - parser = argparse.ArgumentParser( - description="Measure the touchpad size" + parser = argparse.ArgumentParser(description="Measure the touchpad size") + parser.add_argument( + "size", + metavar="WxH", + type=dimension, + help="Touchpad size (width by height) in mm", ) parser.add_argument( - 'size', metavar='WxH', type=dimension, - help='Touchpad size (width by height) in mm', - ) - parser.add_argument( - 'path', metavar='/dev/input/event0', nargs='?', type=str, - help='Path to device (optional)' + "path", + metavar="/dev/input/event0", + nargs="?", + type=str, + help="Path to device (optional)", ) context = pyudev.Context() args = parser.parse_args() if not args.path: - for device in context.list_devices(subsystem='input'): - if (device.get('ID_INPUT_TOUCHPAD', 0) and - (device.device_node or '').startswith('/dev/input/event')): + for device in context.list_devices(subsystem="input"): + if device.get("ID_INPUT_TOUCHPAD", 0) and ( + device.device_node or "" + ).startswith("/dev/input/event"): args.path = device.device_node - name = 'unknown' + name = "unknown" parent = device while parent is not None: - n = parent.get('NAME', None) + n = parent.get("NAME", None) if n: name = n break parent = parent.parent - print('Using {}: {}'.format(name, device.device_node)) + print("Using {}: {}".format(name, device.device_node)) break else: - print('Unable to find a touchpad device.', file=sys.stderr) + print("Unable to find a touchpad device.", file=sys.stderr) return 1 dev = pyudev.Devices.from_device_file(context, args.path) - overrides = [p for p in dev.properties if p.startswith('EVDEV_ABS')] + overrides = [p for p in dev.properties if p.startswith("EVDEV_ABS")] if overrides: print() - print('********************************************************************') - print('WARNING: axis overrides already in place for this device:') + print("********************************************************************") + print("WARNING: axis overrides already in place for this device:") for prop in overrides: - print(' {}={}'.format(prop, dev.properties[prop])) - print('The systemd hwdb already overrides the axis ranges and/or resolution.') - print('This tool is not needed unless you want to verify the axis overrides.') - print('********************************************************************') + print(" {}={}".format(prop, dev.properties[prop])) + print("The systemd hwdb already overrides the axis ranges and/or resolution.") + print("This tool is not needed unless you want to verify the axis overrides.") + print("********************************************************************") print() try: - fd = open(args.path, 'rb') + fd = open(args.path, "rb") evdev = libevdev.Device(fd) touchpad = Touchpad(evdev) - print('Kernel specified touchpad size: {:.1f}x{:.1f}mm'.format(touchpad.width, touchpad.height)) - print('User specified touchpad size: {:.1f}x{:.1f}mm'.format(*args.size)) + print( + "Kernel specified touchpad size: {:.1f}x{:.1f}mm".format( + touchpad.width, touchpad.height + ) + ) + print("User specified touchpad size: {:.1f}x{:.1f}mm".format(*args.size)) print() - print('Kernel axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( - touchpad.x.minimum, touchpad.x.maximum, - touchpad.y.minimum, touchpad.y.maximum)) + print( + "Kernel axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format( + touchpad.x.minimum, + touchpad.x.maximum, + touchpad.y.minimum, + touchpad.y.maximum, + ) + ) - print('Put your finger on the touchpad to start\033[1A') + print("Put your finger on the touchpad to start\033[1A") try: touchpad.draw() @@ -264,15 +285,27 @@ def main(args): touchpad.erase() touchpad.update_from_data() - print('Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]'.format( - touchpad.x.minimum, touchpad.x.maximum, - touchpad.y.minimum, touchpad.y.maximum)) + print( + "Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format( + touchpad.x.minimum, + touchpad.x.maximum, + touchpad.y.minimum, + touchpad.y.maximum, + ) + ) - touchpad.x.resolution = round((touchpad.x.maximum - touchpad.x.minimum) / args.size[0]) - touchpad.y.resolution = round((touchpad.y.maximum - touchpad.y.minimum) / args.size[1]) + touchpad.x.resolution = round( + (touchpad.x.maximum - touchpad.x.minimum) / args.size[0] + ) + touchpad.y.resolution = round( + (touchpad.y.maximum - touchpad.y.minimum) / args.size[1] + ) - print('Resolutions calculated based on user-specified size: x {}, y {} units/mm'.format( - touchpad.x.resolution, touchpad.y.resolution)) + print( + "Resolutions calculated based on user-specified size: x {}, y {} units/mm".format( + touchpad.x.resolution, touchpad.y.resolution + ) + ) # If both x/y are within some acceptable deviation, we skip the axis # overrides and only override the resolution @@ -287,50 +320,73 @@ def main(args): if skip: print() - print('Note: Axis ranges within acceptable deviation, skipping min/max override') + print( + "Note: Axis ranges within acceptable deviation, skipping min/max override" + ) print() print() - print('Suggested hwdb entry:') + print("Suggested hwdb entry:") - use_dmi = evdev.id['bustype'] not in [0x03, 0x05] # USB, Bluetooth + use_dmi = evdev.id["bustype"] not in [0x03, 0x05] # USB, Bluetooth if use_dmi: - modalias = open('/sys/class/dmi/id/modalias').read().strip() - print('Note: the dmi modalias match is a guess based on your machine\'s modalias:') - print(' ', modalias) - print('Please verify that this is the most sensible match and adjust if necessary.') + modalias = open("/sys/class/dmi/id/modalias").read().strip() + print( + "Note: the dmi modalias match is a guess based on your machine's modalias:" + ) + print(" ", modalias) + print( + "Please verify that this is the most sensible match and adjust if necessary." + ) - print('-8<--------------------------') - print('# Laptop model description (e.g. Lenovo X1 Carbon 5th)') + print("-8<--------------------------") + print("# Laptop model description (e.g. Lenovo X1 Carbon 5th)") if use_dmi: - print('evdev:name:{}:{}*'.format(evdev.name, dmi_modalias_match(modalias))) + print("evdev:name:{}:{}*".format(evdev.name, dmi_modalias_match(modalias))) else: - print('evdev:input:b{:04X}v{:04X}p{:04X}*'.format( - evdev.id['bustype'], evdev.id['vendor'], evdev.id['product'])) - print(' EVDEV_ABS_00={}:{}:{}'.format( - touchpad.x.minimum if not skip else '', - touchpad.x.maximum if not skip else '', - touchpad.x.resolution)) - print(' EVDEV_ABS_01={}:{}:{}'.format( - touchpad.y.minimum if not skip else '', - touchpad.y.maximum if not skip else '', - touchpad.y.resolution)) + print( + "evdev:input:b{:04X}v{:04X}p{:04X}*".format( + evdev.id["bustype"], evdev.id["vendor"], evdev.id["product"] + ) + ) + print( + " EVDEV_ABS_00={}:{}:{}".format( + touchpad.x.minimum if not skip else "", + touchpad.x.maximum if not skip else "", + touchpad.x.resolution, + ) + ) + print( + " EVDEV_ABS_01={}:{}:{}".format( + touchpad.y.minimum if not skip else "", + touchpad.y.maximum if not skip else "", + touchpad.y.resolution, + ) + ) if evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]: - print(' EVDEV_ABS_35={}:{}:{}'.format( - touchpad.x.minimum if not skip else '', - touchpad.x.maximum if not skip else '', - touchpad.x.resolution)) - print(' EVDEV_ABS_36={}:{}:{}'.format( - touchpad.y.minimum if not skip else '', - touchpad.y.maximum if not skip else '', - touchpad.y.resolution)) - print('-8<--------------------------') - print('Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb') + print( + " EVDEV_ABS_35={}:{}:{}".format( + touchpad.x.minimum if not skip else "", + touchpad.x.maximum if not skip else "", + touchpad.x.resolution, + ) + ) + print( + " EVDEV_ABS_36={}:{}:{}".format( + touchpad.y.minimum if not skip else "", + touchpad.y.maximum if not skip else "", + touchpad.y.resolution, + ) + ) + print("-8<--------------------------") + print( + "Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb" + ) except DeviceError as e: - print('Error: {}'.format(e), file=sys.stderr) + print("Error: {}".format(e), file=sys.stderr) return 1 except PermissionError: - print('Unable to open device. Please run me as root', file=sys.stderr) + print("Unable to open device. Please run me as root", file=sys.stderr) return 1 return 0 diff --git a/tools/libinput-measure-touchpad-tap.py b/tools/libinput-measure-touchpad-tap.py index fb1b416b..c33acfc7 100755 --- a/tools/libinput-measure-touchpad-tap.py +++ b/tools/libinput-measure-touchpad-tap.py @@ -26,14 +26,17 @@ import sys import argparse + try: import libevdev import textwrap import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(e), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(e), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) print_dest = sys.stdout @@ -66,7 +69,7 @@ class Touch(object): @up.setter def up(self, up): - assert(up > self.down) + assert up > self.down self._up = up @property @@ -88,7 +91,7 @@ class Device(libevdev.Device): self.path = self._find_touch_device() else: self.path = path - fd = open(self.path, 'rb') + fd = open(self.path, "rb") super().__init__(fd) print("Using {}: {}\n".format(self.name, self.path)) @@ -101,18 +104,19 @@ class Device(libevdev.Device): def _find_touch_device(self): context = pyudev.Context() device_node = None - for device in context.list_devices(subsystem='input'): - if (not device.device_node or - not device.device_node.startswith('/dev/input/event')): + for device in context.list_devices(subsystem="input"): + if not device.device_node or not device.device_node.startswith( + "/dev/input/event" + ): continue # pick the touchpad by default, fallback to the first # touchscreen only when there is no touchpad - if device.get('ID_INPUT_TOUCHPAD', 0): + if device.get("ID_INPUT_TOUCHPAD", 0): device_node = device.device_node break - if device.get('ID_INPUT_TOUCHSCREEN', 0) and device_node is None: + if device.get("ID_INPUT_TOUCHSCREEN", 0) and device_node is None: device_node = device.device_node if device_node is not None: @@ -127,17 +131,19 @@ class Device(libevdev.Device): self.touches.append(t) else: self.touches[-1].up = tv2us(event.sec, event.usec) - msg("\rTouch sequences detected: {}".format(len(self.touches)), - end='') + msg("\rTouch sequences detected: {}".format(len(self.touches)), end="") def handle_key(self, event): - tapcodes = [libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, - libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, - libevdev.EV_KEY.BTN_TOOL_QUADTAP, - libevdev.EV_KEY.BTN_TOOL_QUINTTAP] + tapcodes = [ + libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, + libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, + libevdev.EV_KEY.BTN_TOOL_QUADTAP, + libevdev.EV_KEY.BTN_TOOL_QUINTTAP, + ] if event.code in tapcodes and event.value > 0: - error("\rThis tool cannot handle multiple fingers, " - "output will be invalid") + error( + "\rThis tool cannot handle multiple fingers, " "output will be invalid" + ) return if event.matches(libevdev.EV_KEY.BTN_TOUCH): @@ -146,9 +152,11 @@ class Device(libevdev.Device): def handle_syn(self, event): if self.touch.dirty: self.current_sequence().append(self.touch) - self.touch = Touch(major=self.touch.major, - minor=self.touch.minor, - orientation=self.touch.orientation) + self.touch = Touch( + major=self.touch.major, + minor=self.touch.minor, + orientation=self.touch.orientation, + ) def handle_event(self, event): if event.matches(libevdev.EV_KEY): @@ -182,7 +190,9 @@ class Device(libevdev.Device): def print_dat(self): print("# libinput-measure-touchpad-tap") - print(textwrap.dedent('''\ + print( + textwrap.dedent( + """\ # File contents: # This file contains multiple prints of the data in # different sort order. Row number is index of touch @@ -197,7 +207,9 @@ class Device(libevdev.Device): # 4: touch down time in ms, offset by first event # 5: touch up time in ms, offset by first event # 6: time delta in ms - ''')) + """ + ) + ) deltas = [t for t in self.touches] deltas_sorted = sorted(deltas, key=lambda t: t.tdelta) @@ -205,28 +217,44 @@ class Device(libevdev.Device): offset = deltas[0].down for t1, t2 in zip(deltas, deltas_sorted): - print(t1.down - offset, t1.up - offset, t1.tdelta, - t2.down - offset, t2.up - offset, t2.tdelta) + print( + t1.down - offset, + t1.up - offset, + t1.tdelta, + t2.down - offset, + t2.up - offset, + t2.tdelta, + ) def print(self, format): if not self.touches: error("No tap data available") return - if format == 'summary': + if format == "summary": self.print_summary() - elif format == 'dat': + elif format == "dat": self.print_dat() def main(args): - parser = argparse.ArgumentParser(description="Measure tap-to-click properties of devices") - parser.add_argument('path', metavar='/dev/input/event0', - nargs='?', type=str, help='Path to device (optional)') - parser.add_argument('--format', metavar='format', - choices=['summary', 'dat'], - default='summary', - help='data format to print ("summary" or "dat")') + parser = argparse.ArgumentParser( + description="Measure tap-to-click properties of devices" + ) + parser.add_argument( + "path", + metavar="/dev/input/event0", + nargs="?", + type=str, + help="Path to device (optional)", + ) + parser.add_argument( + "--format", + metavar="format", + choices=["summary", "dat"], + default="summary", + help='data format to print ("summary" or "dat")', + ) args = parser.parse_args() if not sys.stdout.isatty(): @@ -235,13 +263,15 @@ def main(args): try: device = Device(args.path) - error("Ready for recording data.\n" - "Tap the touchpad multiple times with a single finger only.\n" - "For useful data we recommend at least 20 taps.\n" - "Ctrl+C to exit") + error( + "Ready for recording data.\n" + "Tap the touchpad multiple times with a single finger only.\n" + "For useful data we recommend at least 20 taps.\n" + "Ctrl+C to exit" + ) device.read_events() except KeyboardInterrupt: - msg('') + msg("") device.print(args.format) except (PermissionError, OSError) as e: error("Error: failed to open device. {}".format(e)) diff --git a/tools/libinput-record-verify-yaml.py b/tools/libinput-record-verify-yaml.py index a40ca5e9..ff9717a2 100755 --- a/tools/libinput-record-verify-yaml.py +++ b/tools/libinput-record-verify-yaml.py @@ -34,7 +34,7 @@ from pkg_resources import parse_version class TestYaml(unittest.TestCase): - filename = '' + filename = "" @classmethod def setUpClass(cls): @@ -42,163 +42,166 @@ class TestYaml(unittest.TestCase): cls.yaml = yaml.safe_load(f) def dict_key_crosscheck(self, d, keys): - '''Check that each key in d is in keys, and that each key is in d''' + """Check that each key in d is in keys, and that each key is in d""" self.assertEqual(sorted(d.keys()), sorted(keys)) def libinput_events(self, filter=None): - '''Returns all libinput events in the recording, regardless of the - device''' - devices = self.yaml['devices'] + """Returns all libinput events in the recording, regardless of the + device""" + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: try: - libinput = e['libinput'] + libinput = e["libinput"] except KeyError: continue for ev in libinput: - if (filter is None or ev['type'] == filter or - isinstance(filter, list) and ev['type'] in filter): + if ( + filter is None + or ev["type"] == filter + or isinstance(filter, list) + and ev["type"] in filter + ): yield ev def test_sections_exist(self): - sections = ['version', 'ndevices', 'libinput', 'system', 'devices'] + sections = ["version", "ndevices", "libinput", "system", "devices"] for section in sections: self.assertIn(section, self.yaml) def test_version(self): - version = self.yaml['version'] + version = self.yaml["version"] self.assertTrue(isinstance(version, int)) self.assertEqual(version, 1) def test_ndevices(self): - ndevices = self.yaml['ndevices'] + ndevices = self.yaml["ndevices"] self.assertTrue(isinstance(ndevices, int)) self.assertGreaterEqual(ndevices, 1) - self.assertEqual(ndevices, len(self.yaml['devices'])) + self.assertEqual(ndevices, len(self.yaml["devices"])) def test_libinput(self): - libinput = self.yaml['libinput'] - version = libinput['version'] + libinput = self.yaml["libinput"] + version = libinput["version"] self.assertTrue(isinstance(version, str)) - self.assertGreaterEqual(parse_version(version), - parse_version('1.10.0')) - git = libinput['git'] + self.assertGreaterEqual(parse_version(version), parse_version("1.10.0")) + git = libinput["git"] self.assertTrue(isinstance(git, str)) - self.assertNotEqual(git, 'unknown') + self.assertNotEqual(git, "unknown") def test_system(self): - system = self.yaml['system'] - kernel = system['kernel'] + system = self.yaml["system"] + kernel = system["kernel"] self.assertTrue(isinstance(kernel, str)) self.assertEqual(kernel, os.uname().release) - dmi = system['dmi'] + dmi = system["dmi"] self.assertTrue(isinstance(dmi, str)) - with open('/sys/class/dmi/id/modalias') as f: + with open("/sys/class/dmi/id/modalias") as f: sys_dmi = f.read()[:-1] # trailing newline self.assertEqual(dmi, sys_dmi) def test_devices_sections_exist(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - self.assertIn('node', d) - self.assertIn('evdev', d) - self.assertIn('udev', d) + self.assertIn("node", d) + self.assertIn("evdev", d) + self.assertIn("udev", d) def test_evdev_sections_exist(self): - sections = ['name', 'id', 'codes', 'properties'] - devices = self.yaml['devices'] + sections = ["name", "id", "codes", "properties"] + devices = self.yaml["devices"] for d in devices: - evdev = d['evdev'] + evdev = d["evdev"] for s in sections: self.assertIn(s, evdev) def test_evdev_name(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - evdev = d['evdev'] - name = evdev['name'] + evdev = d["evdev"] + name = evdev["name"] self.assertTrue(isinstance(name, str)) self.assertGreaterEqual(len(name), 5) def test_evdev_id(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - evdev = d['evdev'] - id = evdev['id'] + evdev = d["evdev"] + id = evdev["id"] self.assertTrue(isinstance(id, list)) self.assertEqual(len(id), 4) self.assertGreater(id[0], 0) self.assertGreater(id[1], 0) def test_evdev_properties(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - evdev = d['evdev'] - properties = evdev['properties'] + evdev = d["evdev"] + properties = evdev["properties"] self.assertTrue(isinstance(properties, list)) def test_hid(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - hid = d['hid'] + hid = d["hid"] self.assertTrue(isinstance(hid, list)) for byte in hid: self.assertGreaterEqual(byte, 0) self.assertLessEqual(byte, 255) def test_udev_sections_exist(self): - sections = ['properties'] - devices = self.yaml['devices'] + sections = ["properties"] + devices = self.yaml["devices"] for d in devices: - udev = d['udev'] + udev = d["udev"] for s in sections: self.assertIn(s, udev) def test_udev_properties(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - udev = d['udev'] - properties = udev['properties'] + udev = d["udev"] + properties = udev["properties"] self.assertTrue(isinstance(properties, list)) self.assertGreater(len(properties), 0) - self.assertIn('ID_INPUT=1', properties) + self.assertIn("ID_INPUT=1", properties) for p in properties: - self.assertTrue(re.match('[A-Z0-9_]+=.+', p)) + self.assertTrue(re.match("[A-Z0-9_]+=.+", p)) def test_udev_id_inputs(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - udev = d['udev'] - properties = udev['properties'] - id_inputs = [p for p in properties if p.startswith('ID_INPUT')] + udev = d["udev"] + properties = udev["properties"] + id_inputs = [p for p in properties if p.startswith("ID_INPUT")] # We expect ID_INPUT and ID_INPUT_something, but might get more # than one of the latter self.assertGreaterEqual(len(id_inputs), 2) def test_events_have_section(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: - self.assertTrue('evdev' in e or 'libinput' in e) + self.assertTrue("evdev" in e or "libinput" in e) def test_events_evdev(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: try: - evdev = e['evdev'] + evdev = e["evdev"] except KeyError: continue @@ -213,28 +216,28 @@ class TestYaml(unittest.TestCase): self.assertLessEqual(ev_syn[4], 1) def test_events_evdev_syn_report(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: try: - evdev = e['evdev'] + evdev = e["evdev"] except KeyError: continue for ev in evdev[:-1]: self.assertFalse(ev[2] == 0 and ev[3] == 0) def test_events_libinput(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: try: - libinput = e['libinput'] + libinput = e["libinput"] except KeyError: continue @@ -243,20 +246,35 @@ class TestYaml(unittest.TestCase): self.assertTrue(isinstance(ev, dict)) def test_events_libinput_type(self): - types = ['POINTER_MOTION', 'POINTER_MOTION_ABSOLUTE', 'POINTER_AXIS', - 'POINTER_BUTTON', 'DEVICE_ADDED', 'KEYBOARD_KEY', - 'TOUCH_DOWN', 'TOUCH_MOTION', 'TOUCH_UP', 'TOUCH_FRAME', - 'GESTURE_SWIPE_BEGIN', 'GESTURE_SWIPE_UPDATE', - 'GESTURE_SWIPE_END', 'GESTURE_PINCH_BEGIN', - 'GESTURE_PINCH_UPDATE', 'GESTURE_PINCH_END', - 'TABLET_TOOL_AXIS', 'TABLET_TOOL_PROXIMITY', - 'TABLET_TOOL_BUTTON', 'TABLET_TOOL_TIP', - 'TABLET_PAD_STRIP', 'TABLET_PAD_RING', - 'TABLET_PAD_BUTTON', 'SWITCH_TOGGLE', - ] + types = [ + "POINTER_MOTION", + "POINTER_MOTION_ABSOLUTE", + "POINTER_AXIS", + "POINTER_BUTTON", + "DEVICE_ADDED", + "KEYBOARD_KEY", + "TOUCH_DOWN", + "TOUCH_MOTION", + "TOUCH_UP", + "TOUCH_FRAME", + "GESTURE_SWIPE_BEGIN", + "GESTURE_SWIPE_UPDATE", + "GESTURE_SWIPE_END", + "GESTURE_PINCH_BEGIN", + "GESTURE_PINCH_UPDATE", + "GESTURE_PINCH_END", + "TABLET_TOOL_AXIS", + "TABLET_TOOL_PROXIMITY", + "TABLET_TOOL_BUTTON", + "TABLET_TOOL_TIP", + "TABLET_PAD_STRIP", + "TABLET_PAD_RING", + "TABLET_PAD_BUTTON", + "SWITCH_TOGGLE", + ] for e in self.libinput_events(): - self.assertIn('type', e) - self.assertIn(e['type'], types) + self.assertIn("type", e) + self.assertIn(e["type"], types) def test_events_libinput_time(self): # DEVICE_ADDED has no time @@ -264,51 +282,51 @@ class TestYaml(unittest.TestCase): # libinput event. try: for e in list(self.libinput_events())[2:]: - self.assertIn('time', e) - self.assertGreater(e['time'], 0.0) - self.assertLess(e['time'], 60.0) + self.assertIn("time", e) + self.assertGreater(e["time"], 0.0) + self.assertLess(e["time"], 60.0) except IndexError: pass def test_events_libinput_device_added(self): - keys = ['type', 'seat', 'logical_seat'] - for e in self.libinput_events('DEVICE_ADDED'): + keys = ["type", "seat", "logical_seat"] + for e in self.libinput_events("DEVICE_ADDED"): self.dict_key_crosscheck(e, keys) - self.assertEqual(e['seat'], 'seat0') - self.assertEqual(e['logical_seat'], 'default') + self.assertEqual(e["seat"], "seat0") + self.assertEqual(e["logical_seat"], "default") def test_events_libinput_pointer_motion(self): - keys = ['type', 'time', 'delta', 'unaccel'] - for e in self.libinput_events('POINTER_MOTION'): + keys = ["type", "time", "delta", "unaccel"] + for e in self.libinput_events("POINTER_MOTION"): self.dict_key_crosscheck(e, keys) - delta = e['delta'] + delta = e["delta"] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) - unaccel = e['unaccel'] + unaccel = e["unaccel"] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) def test_events_libinput_pointer_button(self): - keys = ['type', 'time', 'button', 'state', 'seat_count'] - for e in self.libinput_events('POINTER_BUTTON'): + keys = ["type", "time", "button", "state", "seat_count"] + for e in self.libinput_events("POINTER_BUTTON"): self.dict_key_crosscheck(e, keys) - button = e['button'] + button = e["button"] self.assertGreater(button, 0x100) # BTN_0 self.assertLess(button, 0x160) # KEY_OK - state = e['state'] - self.assertIn(state, ['pressed', 'released']) - scount = e['seat_count'] + state = e["state"] + self.assertIn(state, ["pressed", "released"]) + scount = e["seat_count"] self.assertGreaterEqual(scount, 0) def test_events_libinput_pointer_absolute(self): - keys = ['type', 'time', 'point', 'transformed'] - for e in self.libinput_events('POINTER_MOTION_ABSOLUTE'): + keys = ["type", "time", "point", "transformed"] + for e in self.libinput_events("POINTER_MOTION_ABSOLUTE"): self.dict_key_crosscheck(e, keys) - point = e['point'] + point = e["point"] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: @@ -316,7 +334,7 @@ class TestYaml(unittest.TestCase): self.assertGreater(p, 0.0) self.assertLess(p, 300.0) - transformed = e['transformed'] + transformed = e["transformed"] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: @@ -325,25 +343,24 @@ class TestYaml(unittest.TestCase): self.assertLess(t, 100.0) def test_events_libinput_touch(self): - keys = ['type', 'time', 'slot', 'seat_slot'] + keys = ["type", "time", "slot", "seat_slot"] for e in self.libinput_events(): - if (not e['type'].startswith('TOUCH_') or - e['type'] == 'TOUCH_FRAME'): + if not e["type"].startswith("TOUCH_") or e["type"] == "TOUCH_FRAME": continue for k in keys: self.assertIn(k, e.keys()) - slot = e['slot'] - seat_slot = e['seat_slot'] + slot = e["slot"] + seat_slot = e["seat_slot"] self.assertGreaterEqual(slot, 0) self.assertGreaterEqual(seat_slot, 0) def test_events_libinput_touch_down(self): - keys = ['type', 'time', 'slot', 'seat_slot', 'point', 'transformed'] - for e in self.libinput_events('TOUCH_DOWN'): + keys = ["type", "time", "slot", "seat_slot", "point", "transformed"] + for e in self.libinput_events("TOUCH_DOWN"): self.dict_key_crosscheck(e, keys) - point = e['point'] + point = e["point"] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: @@ -351,7 +368,7 @@ class TestYaml(unittest.TestCase): self.assertGreater(p, 0.0) self.assertLess(p, 300.0) - transformed = e['transformed'] + transformed = e["transformed"] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: @@ -360,10 +377,10 @@ class TestYaml(unittest.TestCase): self.assertLess(t, 100.0) def test_events_libinput_touch_motion(self): - keys = ['type', 'time', 'slot', 'seat_slot', 'point', 'transformed'] - for e in self.libinput_events('TOUCH_MOTION'): + keys = ["type", "time", "slot", "seat_slot", "point", "transformed"] + for e in self.libinput_events("TOUCH_MOTION"): self.dict_key_crosscheck(e, keys) - point = e['point'] + point = e["point"] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: @@ -371,7 +388,7 @@ class TestYaml(unittest.TestCase): self.assertGreater(p, 0.0) self.assertLess(p, 300.0) - transformed = e['transformed'] + transformed = e["transformed"] self.assertTrue(isinstance(transformed, list)) self.assertEqual(len(transformed), 2) for t in transformed: @@ -380,25 +397,25 @@ class TestYaml(unittest.TestCase): self.assertLess(t, 100.0) def test_events_libinput_touch_frame(self): - devices = self.yaml['devices'] + devices = self.yaml["devices"] for d in devices: - events = d['events'] + events = d["events"] if not events: raise unittest.SkipTest() for e in events: try: - evdev = e['libinput'] + evdev = e["libinput"] except KeyError: continue need_frame = False for ev in evdev: - t = ev['type'] - if not t.startswith('TOUCH_'): + t = ev["type"] + if not t.startswith("TOUCH_"): self.assertFalse(need_frame) continue - if t == 'TOUCH_FRAME': + if t == "TOUCH_FRAME": self.assertTrue(need_frame) need_frame = False else: @@ -407,177 +424,175 @@ class TestYaml(unittest.TestCase): self.assertFalse(need_frame) def test_events_libinput_gesture_pinch(self): - keys = ['type', 'time', 'nfingers', 'delta', - 'unaccel', 'angle_delta', 'scale'] - for e in self.libinput_events(['GESTURE_PINCH_BEGIN', - 'GESTURE_PINCH_UPDATE', - 'GESTURE_PINCH_END']): + keys = ["type", "time", "nfingers", "delta", "unaccel", "angle_delta", "scale"] + for e in self.libinput_events( + ["GESTURE_PINCH_BEGIN", "GESTURE_PINCH_UPDATE", "GESTURE_PINCH_END"] + ): self.dict_key_crosscheck(e, keys) - delta = e['delta'] + delta = e["delta"] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) - unaccel = e['unaccel'] + unaccel = e["unaccel"] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) - adelta = e['angle_delta'] + adelta = e["angle_delta"] self.assertTrue(isinstance(adelta, list)) self.assertEqual(len(adelta), 2) for d in adelta: self.assertTrue(isinstance(d, float)) - scale = e['scale'] + scale = e["scale"] self.assertTrue(isinstance(scale, list)) self.assertEqual(len(scale), 2) for d in scale: self.assertTrue(isinstance(d, float)) def test_events_libinput_gesture_swipe(self): - keys = ['type', 'time', 'nfingers', 'delta', - 'unaccel'] - for e in self.libinput_events(['GESTURE_SWIPE_BEGIN', - 'GESTURE_SWIPE_UPDATE', - 'GESTURE_SWIPE_END']): + keys = ["type", "time", "nfingers", "delta", "unaccel"] + for e in self.libinput_events( + ["GESTURE_SWIPE_BEGIN", "GESTURE_SWIPE_UPDATE", "GESTURE_SWIPE_END"] + ): self.dict_key_crosscheck(e, keys) - delta = e['delta'] + delta = e["delta"] self.assertTrue(isinstance(delta, list)) self.assertEqual(len(delta), 2) for d in delta: self.assertTrue(isinstance(d, float)) - unaccel = e['unaccel'] + unaccel = e["unaccel"] self.assertTrue(isinstance(unaccel, list)) self.assertEqual(len(unaccel), 2) for d in unaccel: self.assertTrue(isinstance(d, float)) def test_events_libinput_tablet_pad_button(self): - keys = ['type', 'time', 'button', 'state', 'mode', 'is-toggle'] + keys = ["type", "time", "button", "state", "mode", "is-toggle"] - for e in self.libinput_events('TABLET_PAD_BUTTON'): + for e in self.libinput_events("TABLET_PAD_BUTTON"): self.dict_key_crosscheck(e, keys) - b = e['button'] + b = e["button"] self.assertTrue(isinstance(b, int)) self.assertGreaterEqual(b, 0) self.assertLessEqual(b, 16) - state = e['state'] - self.assertIn(state, ['pressed', 'released']) + state = e["state"] + self.assertIn(state, ["pressed", "released"]) - m = e['mode'] + m = e["mode"] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) - t = e['is-toggle'] + t = e["is-toggle"] self.assertTrue(isinstance(t, bool)) def test_events_libinput_tablet_pad_ring(self): - keys = ['type', 'time', 'number', 'position', 'source', 'mode'] + keys = ["type", "time", "number", "position", "source", "mode"] - for e in self.libinput_events('TABLET_PAD_RING'): + for e in self.libinput_events("TABLET_PAD_RING"): self.dict_key_crosscheck(e, keys) - n = e['number'] + n = e["number"] self.assertTrue(isinstance(n, int)) self.assertGreaterEqual(n, 0) self.assertLessEqual(n, 4) - p = e['position'] + p = e["position"] self.assertTrue(isinstance(p, float)) if p != -1.0: # special 'end' case self.assertGreaterEqual(p, 0.0) self.assertLess(p, 360.0) - m = e['mode'] + m = e["mode"] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) - s = e['source'] - self.assertIn(s, ['finger', 'unknown']) + s = e["source"] + self.assertIn(s, ["finger", "unknown"]) def test_events_libinput_tablet_pad_strip(self): - keys = ['type', 'time', 'number', 'position', 'source', 'mode'] + keys = ["type", "time", "number", "position", "source", "mode"] - for e in self.libinput_events('TABLET_PAD_STRIP'): + for e in self.libinput_events("TABLET_PAD_STRIP"): self.dict_key_crosscheck(e, keys) - n = e['number'] + n = e["number"] self.assertTrue(isinstance(n, int)) self.assertGreaterEqual(n, 0) self.assertLessEqual(n, 4) - p = e['position'] + p = e["position"] self.assertTrue(isinstance(p, float)) if p != -1.0: # special 'end' case self.assertGreaterEqual(p, 0.0) self.assertLessEqual(p, 1.0) - m = e['mode'] + m = e["mode"] self.assertTrue(isinstance(m, int)) self.assertGreaterEqual(m, 0) self.assertLessEqual(m, 3) - s = e['source'] - self.assertIn(s, ['finger', 'unknown']) + s = e["source"] + self.assertIn(s, ["finger", "unknown"]) def test_events_libinput_tablet_tool_proximity(self): - keys = ['type', 'time', 'proximity', 'tool-type', 'serial', 'axes'] + keys = ["type", "time", "proximity", "tool-type", "serial", "axes"] - for e in self.libinput_events('TABLET_TOOL_PROXIMITY'): + for e in self.libinput_events("TABLET_TOOL_PROXIMITY"): for k in keys: self.assertIn(k, e) - p = e['proximity'] - self.assertIn(p, ['in', 'out']) + p = e["proximity"] + self.assertIn(p, ["in", "out"]) - p = e['tool-type'] - self.assertIn(p, ['pen', 'eraser', 'brush', 'airbrush', 'mouse', - 'lens', 'unknown']) + p = e["tool-type"] + self.assertIn( + p, ["pen", "eraser", "brush", "airbrush", "mouse", "lens", "unknown"] + ) - s = e['serial'] + s = e["serial"] self.assertTrue(isinstance(s, int)) self.assertGreaterEqual(s, 0) - a = e['axes'] - for ax in e['axes']: - self.assertIn(a, 'pdtrsw') + a = e["axes"] + for ax in e["axes"]: + self.assertIn(a, "pdtrsw") def test_events_libinput_tablet_tool(self): - keys = ['type', 'time', 'tip'] + keys = ["type", "time", "tip"] - for e in self.libinput_events(['TABLET_TOOL_AXIS', - 'TABLET_TOOL_TIP']): + for e in self.libinput_events(["TABLET_TOOL_AXIS", "TABLET_TOOL_TIP"]): for k in keys: self.assertIn(k, e) - t = e['tip'] - self.assertIn(t, ['down', 'up']) + t = e["tip"] + self.assertIn(t, ["down", "up"]) def test_events_libinput_tablet_tool_button(self): - keys = ['type', 'time', 'button', 'state'] + keys = ["type", "time", "button", "state"] - for e in self.libinput_events('TABLET_TOOL_BUTTON'): + for e in self.libinput_events("TABLET_TOOL_BUTTON"): self.dict_key_crosscheck(e, keys) - b = e['button'] + b = e["button"] # STYLUS, STYLUS2, STYLUS3 - self.assertIn(b, [0x14b, 0x14c, 0x139]) + self.assertIn(b, [0x14B, 0x14C, 0x139]) - s = e['state'] - self.assertIn(s, ['pressed', 'released']) + s = e["state"] + self.assertIn(s, ["pressed", "released"]) def test_events_libinput_tablet_tool_axes(self): - for e in self.libinput_events(['TABLET_TOOL_PROXIMITY', - 'TABLET_TOOL_AXIS', - 'TABLET_TOOL_TIP']): + for e in self.libinput_events( + ["TABLET_TOOL_PROXIMITY", "TABLET_TOOL_AXIS", "TABLET_TOOL_TIP"] + ): - point = e['point'] + point = e["point"] self.assertTrue(isinstance(point, list)) self.assertEqual(len(point), 2) for p in point: @@ -585,7 +600,7 @@ class TestYaml(unittest.TestCase): self.assertGreater(p, 0.0) try: - tilt = e['tilt'] + tilt = e["tilt"] self.assertTrue(isinstance(tilt, list)) self.assertEqual(len(tilt), 2) for t in tilt: @@ -594,70 +609,75 @@ class TestYaml(unittest.TestCase): pass try: - d = e['distance'] + d = e["distance"] self.assertTrue(isinstance(d, float)) self.assertGreaterEqual(d, 0.0) - self.assertNotIn('pressure', e) + self.assertNotIn("pressure", e) except KeyError: pass try: - p = e['pressure'] + p = e["pressure"] self.assertTrue(isinstance(p, float)) self.assertGreaterEqual(p, 0.0) - self.assertNotIn('distance', e) + self.assertNotIn("distance", e) except KeyError: pass try: - r = e['rotation'] + r = e["rotation"] self.assertTrue(isinstance(r, float)) self.assertGreaterEqual(r, 0.0) except KeyError: pass try: - s = e['slider'] + s = e["slider"] self.assertTrue(isinstance(s, float)) self.assertGreaterEqual(s, 0.0) except KeyError: pass try: - w = e['wheel'] + w = e["wheel"] self.assertTrue(isinstance(w, float)) self.assertGreaterEqual(w, 0.0) - self.assertIn('wheel-discrete', e) - wd = e['wheel-discrete'] + self.assertIn("wheel-discrete", e) + wd = e["wheel-discrete"] self.assertTrue(isinstance(wd, 1)) self.assertGreaterEqual(wd, 0.0) def sign(x): (1, -1)[x < 0] + self.assertTrue(sign(w), sign(wd)) except KeyError: pass def test_events_libinput_switch(self): - keys = ['type', 'time', 'switch', 'state'] + keys = ["type", "time", "switch", "state"] - for e in self.libinput_events('SWITCH_TOGGLE'): + for e in self.libinput_events("SWITCH_TOGGLE"): self.dict_key_crosscheck(e, keys) - s = e['switch'] + s = e["switch"] self.assertTrue(isinstance(s, int)) self.assertIn(s, [0x00, 0x01]) # yaml converts on/off to true/false - state = e['state'] + state = e["state"] self.assertTrue(isinstance(state, bool)) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Verify a YAML recording') - parser.add_argument('recording', metavar='recorded-file.yaml', - type=str, help='Path to device recording') - parser.add_argument('--verbose', action='store_true') +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Verify a YAML recording") + parser.add_argument( + "recording", + metavar="recorded-file.yaml", + type=str, + help="Path to device recording", + ) + parser.add_argument("--verbose", action="store_true") args, remainder = parser.parse_known_args() TestYaml.filename = args.recording verbosity = 1 diff --git a/tools/libinput-replay b/tools/libinput-replay index 556cf481..b3710c9e 100755 --- a/tools/libinput-replay +++ b/tools/libinput-replay @@ -35,9 +35,11 @@ try: import yaml import pyudev except ModuleNotFoundError as e: - print('Error: {}'.format(e), file=sys.stderr) - print('One or more python modules are missing. Please install those ' - 'modules and re-run this tool.') + print("Error: {}".format(e), file=sys.stderr) + print( + "One or more python modules are missing. Please install those " + "modules and re-run this tool." + ) sys.exit(1) @@ -53,25 +55,27 @@ class YamlException(Exception): def fetch(yaml, key): - '''Helper function to avoid confusing a YAML error with a - normal KeyError bug''' + """Helper function to avoid confusing a YAML error with a + normal KeyError bug""" try: return yaml[key] except KeyError: - raise YamlException('Failed to get \'{}\' from recording.'.format(key)) + raise YamlException("Failed to get '{}' from recording.".format(key)) def check_udev_properties(yaml_data, uinput): - ''' + """ Compare the properties our new uinput device has with the ones from the recording and ring the alarm bell if one of them is off. - ''' - yaml_udev_section = fetch(yaml_data, 'udev') - yaml_udev_props = fetch(yaml_udev_section, 'properties') - yaml_props = {k: v for (k, v) in [prop.split('=', maxsplit=1) for prop in yaml_udev_props]} + """ + yaml_udev_section = fetch(yaml_data, "udev") + yaml_udev_props = fetch(yaml_udev_section, "properties") + yaml_props = { + k: v for (k, v) in [prop.split("=", maxsplit=1) for prop in yaml_udev_props] + } try: # We don't assign this one to virtual devices - del yaml_props['LIBINPUT_DEVICE_GROUP'] + del yaml_props["LIBINPUT_DEVICE_GROUP"] except KeyError: pass @@ -82,42 +86,52 @@ def check_udev_properties(yaml_data, uinput): for name, value in udev_device.properties.items(): if name in yaml_props: if yaml_props[name] != value: - error(f'Warning: udev property mismatch: recording has {name}={yaml_props[name]}, device has {name}={value}') + error( + f"Warning: udev property mismatch: recording has {name}={yaml_props[name]}, device has {name}={value}" + ) del yaml_props[name] else: # The list of properties we add to the recording, see libinput-record.c - prefixes = ("ID_INPUT", "LIBINPUT", "EVDEV_ABS", "MOUSE_DPI", "POINTINGSTICK_") + prefixes = ( + "ID_INPUT", + "LIBINPUT", + "EVDEV_ABS", + "MOUSE_DPI", + "POINTINGSTICK_", + ) for prefix in prefixes: if name.startswith(prefix): - error(f'Warning: unexpected property: {name}={value}') + error(f"Warning: unexpected property: {name}={value}") # the ones we found above were removed from the dict for name, value in yaml_props.items(): - error(f'Warning: device is missing recorded udev property: {name}={value}') + error(f"Warning: device is missing recorded udev property: {name}={value}") def create(device): - evdev = fetch(device, 'evdev') + evdev = fetch(device, "evdev") d = libevdev.Device() - d.name = fetch(evdev, 'name') + d.name = fetch(evdev, "name") - ids = fetch(evdev, 'id') + ids = fetch(evdev, "id") if len(ids) != 4: - raise YamlException('Invalid ID format: {}'.format(ids)) - d.id = dict(zip(['bustype', 'vendor', 'product', 'version'], ids)) + raise YamlException("Invalid ID format: {}".format(ids)) + d.id = dict(zip(["bustype", "vendor", "product", "version"], ids)) - codes = fetch(evdev, 'codes') + codes = fetch(evdev, "codes") for evtype, evcodes in codes.items(): for code in evcodes: data = None if evtype == libevdev.EV_ABS.value: - values = fetch(evdev, 'absinfo')[code] - absinfo = libevdev.InputAbsInfo(minimum=values[0], - maximum=values[1], - fuzz=values[2], - flat=values[3], - resolution=values[4]) + values = fetch(evdev, "absinfo")[code] + absinfo = libevdev.InputAbsInfo( + minimum=values[0], + maximum=values[1], + fuzz=values[2], + flat=values[3], + resolution=values[4], + ) data = absinfo elif evtype == libevdev.EV_REP.value: if code == libevdev.EV_REP.REP_DELAY.value: @@ -126,7 +140,7 @@ def create(device): data = 20 d.enable(libevdev.evbit(evtype, code), data=data) - properties = fetch(evdev, 'properties') + properties = fetch(evdev, "properties") for prop in properties: d.enable(libevdev.propbit(prop)) @@ -140,30 +154,39 @@ def create(device): def print_events(devnode, indent, evs): devnode = os.path.basename(devnode) for e in evs: - print("{}: {}{:06d}.{:06d} {} / {:<20s} {:4d}".format( - devnode, ' ' * (indent * 8), e.sec, e.usec, e.type.name, e.code.name, e.value)) + print( + "{}: {}{:06d}.{:06d} {} / {:<20s} {:4d}".format( + devnode, + " " * (indent * 8), + e.sec, + e.usec, + e.type.name, + e.code.name, + e.value, + ) + ) def replay(device, verbose): - events = fetch(device, 'events') + events = fetch(device, "events") if events is None: return - uinput = device['__uinput'] + uinput = device["__uinput"] # The first event may have a nonzero offset but we want to replay # immediately regardless. When replaying multiple devices, the first # offset is the offset from the first event on any device. - offset = time.time() - device['__first_event_offset'] + offset = time.time() - device["__first_event_offset"] if offset < 0: - error('WARNING: event time offset is in the future, refusing to replay') + error("WARNING: event time offset is in the future, refusing to replay") return # each 'evdev' set contains one SYN_REPORT so we only need to check for # the time offset once per event for event in events: try: - evdev = fetch(event, 'evdev') + evdev = fetch(event, "evdev") except YamlException: continue @@ -174,25 +197,31 @@ def replay(device, verbose): if evtime - now > 150 / 1e6: # 150 µs error margin time.sleep(evtime - now - 150 / 1e6) - evs = [libevdev.InputEvent(libevdev.evbit(e[2], e[3]), value=e[4], sec=e[0], usec=e[1]) for e in evdev] + evs = [ + libevdev.InputEvent( + libevdev.evbit(e[2], e[3]), value=e[4], sec=e[0], usec=e[1] + ) + for e in evdev + ] uinput.send_events(evs) if verbose: - print_events(uinput.devnode, device['__index'], evs) + print_events(uinput.devnode, device["__index"], evs) def first_timestamp(device): try: - events = fetch(device, 'events') + events = fetch(device, "events") if events is None: - raise YamlException('No events from this device') + raise YamlException("No events from this device") - evdev = fetch(events[0], 'evdev') + evdev = fetch(events[0], "evdev") (sec, usec, *_) = evdev[0] - return sec + usec / 1.e6 + return sec + usec / 1.0e6 except YamlException: import math + return math.inf @@ -204,7 +233,7 @@ def wrap(func, *args): def loop(args, recording): - devices = fetch(recording, 'devices') + devices = fetch(recording, "devices") # All devices need to start replaying at the same time, so let's find # the very first event and offset everything by that timestamp. @@ -212,13 +241,13 @@ def loop(args, recording): for idx, d in enumerate(devices): uinput = create(d) - print('{}: {}'.format(uinput.devnode, uinput.name)) - d['__uinput'] = uinput # cheaper to hide it in the dict then work around it - d['__index'] = idx - d['__first_event_offset'] = toffset + print("{}: {}".format(uinput.devnode, uinput.name)) + d["__uinput"] = uinput # cheaper to hide it in the dict then work around it + d["__index"] = idx + d["__first_event_offset"] = toffset while True: - input('Hit enter to start replaying') + input("Hit enter to start replaying") processes = [] for d in devices: @@ -236,66 +265,83 @@ def loop(args, recording): def create_device_quirk(device): try: - quirks = fetch(device, 'quirks') + quirks = fetch(device, "quirks") if not quirks: return None except YamlException: return None # Where the device has a quirk, we match on name, vendor and product. # That's the best match we can assemble here from the info we have. - evdev = fetch(device, 'evdev') - name = fetch(evdev, 'name') - id = fetch(evdev, 'id') - quirk = ('[libinput-replay {name}]\n' - 'MatchName={name}\n' - 'MatchVendor=0x{id[1]:04X}\n' - 'MatchProduct=0x{id[2]:04X}\n').format(name=name, id=id) - quirk += '\n'.join(quirks) + evdev = fetch(device, "evdev") + name = fetch(evdev, "name") + id = fetch(evdev, "id") + quirk = ( + "[libinput-replay {name}]\n" + "MatchName={name}\n" + "MatchVendor=0x{id[1]:04X}\n" + "MatchProduct=0x{id[2]:04X}\n" + ).format(name=name, id=id) + quirk += "\n".join(quirks) return quirk def setup_quirks(recording): - devices = fetch(recording, 'devices') + devices = fetch(recording, "devices") overrides = None quirks = [] for d in devices: - if 'quirks' in d: + if "quirks" in d: quirk = create_device_quirk(d) if quirk: quirks.append(quirk) if not quirks: return None - overrides = Path('/etc/libinput/local-overrides.quirks') + overrides = Path("/etc/libinput/local-overrides.quirks") if overrides.exists(): - print('{} exists, please move it out of the way first'.format(overrides), file=sys.stderr) + print( + "{} exists, please move it out of the way first".format(overrides), + file=sys.stderr, + ) sys.exit(1) overrides.parent.mkdir(exist_ok=True) - with overrides.open('w+') as fd: - fd.write('# This file was generated by libinput replay\n') - fd.write('# Unless libinput replay is running right now, remove this file.\n') - fd.write('\n\n'.join(quirks)) + with overrides.open("w+") as fd: + fd.write("# This file was generated by libinput replay\n") + fd.write("# Unless libinput replay is running right now, remove this file.\n") + fd.write("\n\n".join(quirks)) return overrides def check_file(recording): - version = fetch(recording, 'version') + version = fetch(recording, "version") if version != SUPPORTED_FILE_VERSION: - raise YamlException('Invalid file format: {}, expected {}'.format(version, SUPPORTED_FILE_VERSION)) + raise YamlException( + "Invalid file format: {}, expected {}".format( + version, SUPPORTED_FILE_VERSION + ) + ) - ndevices = fetch(recording, 'ndevices') - devices = fetch(recording, 'devices') + ndevices = fetch(recording, "ndevices") + devices = fetch(recording, "devices") if ndevices != len(devices): - error('WARNING: truncated file, expected {} devices, got {}'.format(ndevices, len(devices))) + error( + "WARNING: truncated file, expected {} devices, got {}".format( + ndevices, len(devices) + ) + ) def main(): - parser = argparse.ArgumentParser(description='Replay a device recording') - parser.add_argument('recording', metavar='recorded-file.yaml', - type=str, help='Path to device recording') - parser.add_argument('--verbose', action='store_true') + parser = argparse.ArgumentParser(description="Replay a device recording") + parser.add_argument( + "recording", + metavar="recorded-file.yaml", + type=str, + help="Path to device recording", + ) + parser.add_argument("--verbose", action="store_true") args = parser.parse_args() quirks_file = None @@ -309,13 +355,13 @@ def main(): except KeyboardInterrupt: pass except (PermissionError, OSError) as e: - error('Error: failed to open device: {}'.format(e)) + error("Error: failed to open device: {}".format(e)) except YamlException as e: - error('Error: failed to parse recording: {}'.format(e)) + error("Error: failed to parse recording: {}".format(e)) finally: if quirks_file: quirks_file.unlink() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/tools/test_tool_option_parsing.py b/tools/test_tool_option_parsing.py index 373aa8b9..5c92b11a 100755 --- a/tools/test_tool_option_parsing.py +++ b/tools/test_tool_option_parsing.py @@ -32,15 +32,15 @@ import logging try: import pytest except ImportError: - print('Failed to import pytest. Skipping.', file=sys.stderr) + print("Failed to import pytest. Skipping.", file=sys.stderr) sys.exit(77) -logger = logging.getLogger('test') +logger = logging.getLogger("test") logger.setLevel(logging.DEBUG) -if '@DISABLE_WARNING@' != 'yes': - print('This is the source file, run the one in the meson builddir instead') +if "@DISABLE_WARNING@" != "yes": + print("This is the source file, run the one in the meson builddir instead") sys.exit(1) @@ -49,9 +49,13 @@ def _disable_coredump(): def run_command(args): - logger.debug('run command: {}'.format(' '.join(args))) - with subprocess.Popen(args, preexec_fn=_disable_coredump, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: + logger.debug("run command: {}".format(" ".join(args))) + with subprocess.Popen( + args, + preexec_fn=_disable_coredump, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) as p: try: p.wait(0.7) except subprocess.TimeoutExpired: @@ -59,11 +63,11 @@ def run_command(args): stdout, stderr = p.communicate(timeout=5) if p.returncode == -3: p.returncode = 0 - return p.returncode, stdout.decode('UTF-8'), stderr.decode('UTF-8') + return p.returncode, stdout.decode("UTF-8"), stderr.decode("UTF-8") class LibinputTool(object): - libinput_tool = 'libinput' + libinput_tool = "libinput" subtool = None def __init__(self, subtool=None): @@ -92,43 +96,43 @@ class LibinputTool(object): def run_command_unrecognized_option(self, args): rc, stdout, stderr = self.run_command(args) assert rc == 2, (rc, stdout, stderr) - assert stdout.startswith('Usage') or stdout == '' - assert 'unrecognized option' in stderr + assert stdout.startswith("Usage") or stdout == "" + assert "unrecognized option" in stderr def run_command_missing_arg(self, args): rc, stdout, stderr = self.run_command(args) assert rc == 2, (rc, stdout, stderr) - assert stdout.startswith('Usage') or stdout == '' - assert 'requires an argument' in stderr + assert stdout.startswith("Usage") or stdout == "" + assert "requires an argument" in stderr def run_command_unrecognized_tool(self, args): rc, stdout, stderr = self.run_command(args) assert rc == 2, (rc, stdout, stderr) - assert stdout.startswith('Usage') or stdout == '' - assert 'is not installed' in stderr + assert stdout.startswith("Usage") or stdout == "" + assert "is not installed" in stderr class LibinputDebugGui(LibinputTool): - def __init__(self, subtool='debug-gui'): - assert subtool == 'debug-gui' + def __init__(self, subtool="debug-gui"): + assert subtool == "debug-gui" super().__init__(subtool) - debug_gui_enabled = '@MESON_ENABLED_DEBUG_GUI@' == 'True' + debug_gui_enabled = "@MESON_ENABLED_DEBUG_GUI@" == "True" if not debug_gui_enabled: pytest.skip() - if not os.getenv('DISPLAY') and not os.getenv('WAYLAND_DISPLAY'): + if not os.getenv("DISPLAY") and not os.getenv("WAYLAND_DISPLAY"): pytest.skip() # 77 means gtk_init() failed, which is probably because you can't # connect to the display server. - rc, _, _ = self.run_command(['--help']) + rc, _, _ = self.run_command(["--help"]) if rc == 77: pytest.skip() def get_tool(subtool=None): - if subtool == 'debug-gui': + if subtool == "debug-gui": return LibinputDebugGui() else: return LibinputTool(subtool) @@ -139,110 +143,110 @@ def libinput(): return get_tool() -@pytest.fixture(params=['debug-events', 'debug-gui']) +@pytest.fixture(params=["debug-events", "debug-gui"]) def libinput_debug_tool(request): yield get_tool(request.param) @pytest.fixture def libinput_debug_events(): - return get_tool('debug-events') + return get_tool("debug-events") @pytest.fixture def libinput_debug_gui(): - return get_tool('debug-gui') + return get_tool("debug-gui") @pytest.fixture def libinput_record(): - return get_tool('record') + return get_tool("record") def test_help(libinput): - stdout, stderr = libinput.run_command_success(['--help']) - assert stdout.startswith('Usage:') - assert stderr == '' + stdout, stderr = libinput.run_command_success(["--help"]) + assert stdout.startswith("Usage:") + assert stderr == "" def test_version(libinput): - stdout, stderr = libinput.run_command_success(['--version']) - assert stdout.startswith('1') - assert stderr == '' + stdout, stderr = libinput.run_command_success(["--version"]) + assert stdout.startswith("1") + assert stderr == "" -@pytest.mark.parametrize('argument', ['--banana', '--foo', '--quiet', '--verbose']) +@pytest.mark.parametrize("argument", ["--banana", "--foo", "--quiet", "--verbose"]) def test_invalid_arguments(libinput, argument): libinput.run_command_unrecognized_option([argument]) -@pytest.mark.parametrize('tool', [['foo'], ['debug'], ['foo', '--quiet']]) +@pytest.mark.parametrize("tool", [["foo"], ["debug"], ["foo", "--quiet"]]) def test_invalid_tool(libinput, tool): libinput.run_command_unrecognized_tool(tool) def test_udev_seat(libinput_debug_tool): - libinput_debug_tool.run_command_missing_arg(['--udev']) - libinput_debug_tool.run_command_success(['--udev', 'seat0']) - libinput_debug_tool.run_command_success(['--udev', 'seat1']) + libinput_debug_tool.run_command_missing_arg(["--udev"]) + libinput_debug_tool.run_command_success(["--udev", "seat0"]) + libinput_debug_tool.run_command_success(["--udev", "seat1"]) -@pytest.mark.skipif(os.environ.get('UDEV_NOT_AVAILABLE'), reason='udev required') +@pytest.mark.skipif(os.environ.get("UDEV_NOT_AVAILABLE"), reason="udev required") def test_device_arg(libinput_debug_tool): - libinput_debug_tool.run_command_missing_arg(['--device']) - libinput_debug_tool.run_command_success(['--device', '/dev/input/event0']) - libinput_debug_tool.run_command_success(['--device', '/dev/input/event1']) - libinput_debug_tool.run_command_success(['/dev/input/event0']) + libinput_debug_tool.run_command_missing_arg(["--device"]) + libinput_debug_tool.run_command_success(["--device", "/dev/input/event0"]) + libinput_debug_tool.run_command_success(["--device", "/dev/input/event1"]) + libinput_debug_tool.run_command_success(["/dev/input/event0"]) options = { - 'pattern': ['sendevents'], + "pattern": ["sendevents"], # enable/disable options - 'enable-disable': [ - 'tap', - 'drag', - 'drag-lock', - 'middlebutton', - 'natural-scrolling', - 'left-handed', - 'dwt' + "enable-disable": [ + "tap", + "drag", + "drag-lock", + "middlebutton", + "natural-scrolling", + "left-handed", + "dwt", ], # options with distinct values - 'enums': { - 'set-click-method': ['none', 'clickfinger', 'buttonareas'], - 'set-scroll-method': ['none', 'twofinger', 'edge', 'button'], - 'set-profile': ['adaptive', 'flat'], - 'set-tap-map': ['lrm', 'lmr'], + "enums": { + "set-click-method": ["none", "clickfinger", "buttonareas"], + "set-scroll-method": ["none", "twofinger", "edge", "button"], + "set-profile": ["adaptive", "flat"], + "set-tap-map": ["lrm", "lmr"], }, # options with a range - 'ranges': { - 'set-speed': (float, -1.0, +1.0), - } + "ranges": { + "set-speed": (float, -1.0, +1.0), + }, } # Options that allow for glob patterns -@pytest.mark.parametrize('option', options['pattern']) +@pytest.mark.parametrize("option", options["pattern"]) def test_options_pattern(libinput_debug_tool, option): - libinput_debug_tool.run_command_success(['--disable-{}'.format(option), '*']) - libinput_debug_tool.run_command_success(['--disable-{}'.format(option), 'abc*']) + libinput_debug_tool.run_command_success(["--disable-{}".format(option), "*"]) + libinput_debug_tool.run_command_success(["--disable-{}".format(option), "abc*"]) -@pytest.mark.parametrize('option', options['enable-disable']) +@pytest.mark.parametrize("option", options["enable-disable"]) def test_options_enable_disable(libinput_debug_tool, option): - libinput_debug_tool.run_command_success(['--enable-{}'.format(option)]) - libinput_debug_tool.run_command_success(['--disable-{}'.format(option)]) + libinput_debug_tool.run_command_success(["--enable-{}".format(option)]) + libinput_debug_tool.run_command_success(["--disable-{}".format(option)]) -@pytest.mark.parametrize('option', options['enums'].items()) +@pytest.mark.parametrize("option", options["enums"].items()) def test_options_enums(libinput_debug_tool, option): name, values = option for v in values: - libinput_debug_tool.run_command_success(['--{}'.format(name), v]) - libinput_debug_tool.run_command_success(['--{}={}'.format(name, v)]) + libinput_debug_tool.run_command_success(["--{}".format(name), v]) + libinput_debug_tool.run_command_success(["--{}={}".format(name, v)]) -@pytest.mark.parametrize('option', options['ranges'].items()) +@pytest.mark.parametrize("option", options["ranges"].items()) def test_options_ranges(libinput_debug_tool, option): name, values = option range_type, minimum, maximum = values @@ -250,114 +254,128 @@ def test_options_ranges(libinput_debug_tool, option): step = (maximum - minimum) / 10.0 value = minimum while value < maximum: - libinput_debug_tool.run_command_success(['--{}'.format(name), str(value)]) - libinput_debug_tool.run_command_success(['--{}={}'.format(name, value)]) + libinput_debug_tool.run_command_success(["--{}".format(name), str(value)]) + libinput_debug_tool.run_command_success(["--{}={}".format(name, value)]) value += step - libinput_debug_tool.run_command_success(['--{}'.format(name), str(maximum)]) - libinput_debug_tool.run_command_success(['--{}={}'.format(name, maximum)]) + libinput_debug_tool.run_command_success(["--{}".format(name), str(maximum)]) + libinput_debug_tool.run_command_success(["--{}={}".format(name, maximum)]) def test_apply_to(libinput_debug_tool): - libinput_debug_tool.run_command_missing_arg(['--apply-to']) - libinput_debug_tool.run_command_success(['--apply-to', '*foo*']) - libinput_debug_tool.run_command_success(['--apply-to', 'foobar']) - libinput_debug_tool.run_command_success(['--apply-to', 'any']) + libinput_debug_tool.run_command_missing_arg(["--apply-to"]) + libinput_debug_tool.run_command_success(["--apply-to", "*foo*"]) + libinput_debug_tool.run_command_success(["--apply-to", "foobar"]) + libinput_debug_tool.run_command_success(["--apply-to", "any"]) -@pytest.mark.parametrize('args', [['--verbose'], ['--quiet'], - ['--verbose', '--quiet'], - ['--quiet', '--verbose']]) +@pytest.mark.parametrize( + "args", + [["--verbose"], ["--quiet"], ["--verbose", "--quiet"], ["--quiet", "--verbose"]], +) def test_debug_events_verbose_quiet(libinput_debug_events, args): libinput_debug_events.run_command_success(args) -@pytest.mark.parametrize('arg', ['--banana', '--foo', '--version']) +@pytest.mark.parametrize("arg", ["--banana", "--foo", "--version"]) def test_invalid_args(libinput_debug_tool, arg): libinput_debug_tool.run_command_unrecognized_option([arg]) def test_libinput_debug_events_multiple_devices(libinput_debug_events): - libinput_debug_events.run_command_success(['--device', '/dev/input/event0', '/dev/input/event1']) + libinput_debug_events.run_command_success( + ["--device", "/dev/input/event0", "/dev/input/event1"] + ) # same event path multiple times? meh, your problem - libinput_debug_events.run_command_success(['--device', '/dev/input/event0', '/dev/input/event0']) - libinput_debug_events.run_command_success(['/dev/input/event0', '/dev/input/event1']) + libinput_debug_events.run_command_success( + ["--device", "/dev/input/event0", "/dev/input/event0"] + ) + libinput_debug_events.run_command_success( + ["/dev/input/event0", "/dev/input/event1"] + ) def test_libinput_debug_events_too_many_devices(libinput_debug_events): # Too many arguments just bails with the usage message - rc, stdout, stderr = libinput_debug_events.run_command(['/dev/input/event0'] * 61) + rc, stdout, stderr = libinput_debug_events.run_command(["/dev/input/event0"] * 61) assert rc == 2, (stdout, stderr) -@pytest.mark.parametrize('arg', ['--quiet']) +@pytest.mark.parametrize("arg", ["--quiet"]) def test_libinput_debug_gui_invalid_arg(libinput_debug_gui, arg): libinput_debug_gui.run_command_unrecognized_option([arg]) def test_libinput_debug_gui_verbose(libinput_debug_gui): - libinput_debug_gui.run_command_success(['--verbose']) + libinput_debug_gui.run_command_success(["--verbose"]) -@pytest.mark.parametrize('arg', ['--help', '--show-keycodes', '--with-libinput']) +@pytest.mark.parametrize("arg", ["--help", "--show-keycodes", "--with-libinput"]) def test_libinput_record_args(libinput_record, arg): libinput_record.run_command_success([arg]) def test_libinput_record_multiple_arg(libinput_record): # this arg is deprecated and a noop - libinput_record.run_command_success(['--multiple']) + libinput_record.run_command_success(["--multiple"]) @pytest.fixture def recording(tmp_path): - return str((tmp_path / 'record.out').resolve()) + return str((tmp_path / "record.out").resolve()) def test_libinput_record_all(libinput_record, recording): - libinput_record.run_command_success(['--all', '-o', recording]) - libinput_record.run_command_success(['--all', recording]) + libinput_record.run_command_success(["--all", "-o", recording]) + libinput_record.run_command_success(["--all", recording]) def test_libinput_record_outfile(libinput_record, recording): - libinput_record.run_command_success(['-o', recording]) - libinput_record.run_command_success(['--output-file', recording]) - libinput_record.run_command_success(['--output-file={}'.format(recording)]) + libinput_record.run_command_success(["-o", recording]) + libinput_record.run_command_success(["--output-file", recording]) + libinput_record.run_command_success(["--output-file={}".format(recording)]) def test_libinput_record_single(libinput_record, recording): - libinput_record.run_command_success(['/dev/input/event0']) - libinput_record.run_command_success(['-o', recording, '/dev/input/event0']) - libinput_record.run_command_success(['/dev/input/event0', recording]) - libinput_record.run_command_success([recording, '/dev/input/event0']) + libinput_record.run_command_success(["/dev/input/event0"]) + libinput_record.run_command_success(["-o", recording, "/dev/input/event0"]) + libinput_record.run_command_success(["/dev/input/event0", recording]) + libinput_record.run_command_success([recording, "/dev/input/event0"]) def test_libinput_record_multiple(libinput_record, recording): - libinput_record.run_command_success(['-o', recording, '/dev/input/event0', '/dev/input/event1']) - libinput_record.run_command_success([recording, '/dev/input/event0', '/dev/input/event1']) - libinput_record.run_command_success(['/dev/input/event0', '/dev/input/event1', recording]) + libinput_record.run_command_success( + ["-o", recording, "/dev/input/event0", "/dev/input/event1"] + ) + libinput_record.run_command_success( + [recording, "/dev/input/event0", "/dev/input/event1"] + ) + libinput_record.run_command_success( + ["/dev/input/event0", "/dev/input/event1", recording] + ) def test_libinput_record_autorestart(libinput_record, recording): - libinput_record.run_command_invalid(['--autorestart']) - libinput_record.run_command_invalid(['--autorestart=2']) - libinput_record.run_command_success(['-o', recording, '--autorestart=2']) + libinput_record.run_command_invalid(["--autorestart"]) + libinput_record.run_command_invalid(["--autorestart=2"]) + libinput_record.run_command_success(["-o", recording, "--autorestart=2"]) def main(): - args = ['-m', 'pytest'] + args = ["-m", "pytest"] try: import xdist # noqa - args += ['-n', 'auto'] + + args += ["-n", "auto"] except ImportError: - logger.info('python-xdist missing, this test will be slow') + logger.info("python-xdist missing, this test will be slow") pass - args += ['@MESON_BUILD_ROOT@'] + args += ["@MESON_BUILD_ROOT@"] - os.environ['LIBINPUT_RUNNING_TEST_SUITE'] = '1' + os.environ["LIBINPUT_RUNNING_TEST_SUITE"] = "1" return subprocess.run([sys.executable] + args).returncode -if __name__ == '__main__': +if __name__ == "__main__": raise SystemExit(main())