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