mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2026-01-11 02:50:24 +01:00
Use python black for all pyhon file formatting
Let's enforce a consistent (and verifiable) style everywhere. Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
This commit is contained in:
parent
c0364f2317
commit
1dbdef8fdb
12 changed files with 1291 additions and 922 deletions
|
|
@ -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)
|
||||
|
||||
|
||||
#
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue