1a46c0ec8Sopenharmony_ci#!/usr/bin/env python3 2a46c0ec8Sopenharmony_ci# vim: set expandtab shiftwidth=4: 3a46c0ec8Sopenharmony_ci# -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */ 4a46c0ec8Sopenharmony_ci# 5a46c0ec8Sopenharmony_ci# Copyright © 2017 Red Hat, Inc. 6a46c0ec8Sopenharmony_ci# 7a46c0ec8Sopenharmony_ci# Permission is hereby granted, free of charge, to any person obtaining a 8a46c0ec8Sopenharmony_ci# copy of this software and associated documentation files (the "Software"), 9a46c0ec8Sopenharmony_ci# to deal in the Software without restriction, including without limitation 10a46c0ec8Sopenharmony_ci# the rights to use, copy, modify, merge, publish, distribute, sublicense, 11a46c0ec8Sopenharmony_ci# and/or sell copies of the Software, and to permit persons to whom the 12a46c0ec8Sopenharmony_ci# Software is furnished to do so, subject to the following conditions: 13a46c0ec8Sopenharmony_ci# 14a46c0ec8Sopenharmony_ci# The above copyright notice and this permission notice (including the next 15a46c0ec8Sopenharmony_ci# paragraph) shall be included in all copies or substantial portions of the 16a46c0ec8Sopenharmony_ci# Software. 17a46c0ec8Sopenharmony_ci# 18a46c0ec8Sopenharmony_ci# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19a46c0ec8Sopenharmony_ci# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20a46c0ec8Sopenharmony_ci# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21a46c0ec8Sopenharmony_ci# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22a46c0ec8Sopenharmony_ci# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23a46c0ec8Sopenharmony_ci# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24a46c0ec8Sopenharmony_ci# DEALINGS IN THE SOFTWARE. 25a46c0ec8Sopenharmony_ci# 26a46c0ec8Sopenharmony_ci 27a46c0ec8Sopenharmony_ciimport sys 28a46c0ec8Sopenharmony_ciimport subprocess 29a46c0ec8Sopenharmony_ciimport argparse 30a46c0ec8Sopenharmony_ci 31a46c0ec8Sopenharmony_citry: 32a46c0ec8Sopenharmony_ci import libevdev 33a46c0ec8Sopenharmony_ci import pyudev 34a46c0ec8Sopenharmony_ciexcept ModuleNotFoundError as e: 35a46c0ec8Sopenharmony_ci print("Error: {}".format(str(e)), file=sys.stderr) 36a46c0ec8Sopenharmony_ci print( 37a46c0ec8Sopenharmony_ci "One or more python modules are missing. Please install those " 38a46c0ec8Sopenharmony_ci "modules and re-run this tool." 39a46c0ec8Sopenharmony_ci ) 40a46c0ec8Sopenharmony_ci sys.exit(1) 41a46c0ec8Sopenharmony_ci 42a46c0ec8Sopenharmony_ci 43a46c0ec8Sopenharmony_ciclass TableFormatter(object): 44a46c0ec8Sopenharmony_ci ALIGNMENT = 3 45a46c0ec8Sopenharmony_ci 46a46c0ec8Sopenharmony_ci def __init__(self): 47a46c0ec8Sopenharmony_ci self.colwidths = [] 48a46c0ec8Sopenharmony_ci 49a46c0ec8Sopenharmony_ci @property 50a46c0ec8Sopenharmony_ci def width(self): 51a46c0ec8Sopenharmony_ci return sum(self.colwidths) + 1 52a46c0ec8Sopenharmony_ci 53a46c0ec8Sopenharmony_ci def headers(self, args): 54a46c0ec8Sopenharmony_ci s = "|" 55a46c0ec8Sopenharmony_ci align = self.ALIGNMENT - 1 # account for | 56a46c0ec8Sopenharmony_ci 57a46c0ec8Sopenharmony_ci for arg in args: 58a46c0ec8Sopenharmony_ci # +2 because we want space left/right of text 59a46c0ec8Sopenharmony_ci w = ((len(arg) + 2 + align) // align) * align 60a46c0ec8Sopenharmony_ci self.colwidths.append(w + 1) 61a46c0ec8Sopenharmony_ci s += " {:^{width}s} |".format(arg, width=w - 2) 62a46c0ec8Sopenharmony_ci 63a46c0ec8Sopenharmony_ci return s 64a46c0ec8Sopenharmony_ci 65a46c0ec8Sopenharmony_ci def values(self, args): 66a46c0ec8Sopenharmony_ci s = "|" 67a46c0ec8Sopenharmony_ci for w, arg in zip(self.colwidths, args): 68a46c0ec8Sopenharmony_ci w -= 1 # width includes | separator 69a46c0ec8Sopenharmony_ci if type(arg) == str: 70a46c0ec8Sopenharmony_ci # We want space margins for strings 71a46c0ec8Sopenharmony_ci s += " {:{width}s} |".format(arg, width=w - 2) 72a46c0ec8Sopenharmony_ci elif type(arg) == bool: 73a46c0ec8Sopenharmony_ci s += "{:^{width}s}|".format("x" if arg else " ", width=w) 74a46c0ec8Sopenharmony_ci else: 75a46c0ec8Sopenharmony_ci s += "{:^{width}d}|".format(arg, width=w) 76a46c0ec8Sopenharmony_ci 77a46c0ec8Sopenharmony_ci if len(args) < len(self.colwidths): 78a46c0ec8Sopenharmony_ci s += "|".rjust(self.width - len(s), " ") 79a46c0ec8Sopenharmony_ci return s 80a46c0ec8Sopenharmony_ci 81a46c0ec8Sopenharmony_ci def separator(self): 82a46c0ec8Sopenharmony_ci return "+" + "-" * (self.width - 2) + "+" 83a46c0ec8Sopenharmony_ci 84a46c0ec8Sopenharmony_ci 85a46c0ec8Sopenharmony_cifmt = TableFormatter() 86a46c0ec8Sopenharmony_ci 87a46c0ec8Sopenharmony_ci 88a46c0ec8Sopenharmony_ciclass Range(object): 89a46c0ec8Sopenharmony_ci """Class to keep a min/max of a value around""" 90a46c0ec8Sopenharmony_ci 91a46c0ec8Sopenharmony_ci def __init__(self): 92a46c0ec8Sopenharmony_ci self.min = float("inf") 93a46c0ec8Sopenharmony_ci self.max = float("-inf") 94a46c0ec8Sopenharmony_ci 95a46c0ec8Sopenharmony_ci def update(self, value): 96a46c0ec8Sopenharmony_ci self.min = min(self.min, value) 97a46c0ec8Sopenharmony_ci self.max = max(self.max, value) 98a46c0ec8Sopenharmony_ci 99a46c0ec8Sopenharmony_ci 100a46c0ec8Sopenharmony_ciclass Touch(object): 101a46c0ec8Sopenharmony_ci """A single data point of a sequence (i.e. one event frame)""" 102a46c0ec8Sopenharmony_ci 103a46c0ec8Sopenharmony_ci def __init__(self, pressure=None): 104a46c0ec8Sopenharmony_ci self.pressure = pressure 105a46c0ec8Sopenharmony_ci 106a46c0ec8Sopenharmony_ci 107a46c0ec8Sopenharmony_ciclass TouchSequence(object): 108a46c0ec8Sopenharmony_ci """A touch sequence from beginning to end""" 109a46c0ec8Sopenharmony_ci 110a46c0ec8Sopenharmony_ci def __init__(self, device, tracking_id): 111a46c0ec8Sopenharmony_ci self.device = device 112a46c0ec8Sopenharmony_ci self.tracking_id = tracking_id 113a46c0ec8Sopenharmony_ci self.points = [] 114a46c0ec8Sopenharmony_ci 115a46c0ec8Sopenharmony_ci self.is_active = True 116a46c0ec8Sopenharmony_ci 117a46c0ec8Sopenharmony_ci self.is_down = False 118a46c0ec8Sopenharmony_ci self.was_down = False 119a46c0ec8Sopenharmony_ci self.is_palm = False 120a46c0ec8Sopenharmony_ci self.was_palm = False 121a46c0ec8Sopenharmony_ci self.is_thumb = False 122a46c0ec8Sopenharmony_ci self.was_thumb = False 123a46c0ec8Sopenharmony_ci 124a46c0ec8Sopenharmony_ci self.prange = Range() 125a46c0ec8Sopenharmony_ci 126a46c0ec8Sopenharmony_ci def append(self, touch): 127a46c0ec8Sopenharmony_ci """Add a Touch to the sequence""" 128a46c0ec8Sopenharmony_ci self.points.append(touch) 129a46c0ec8Sopenharmony_ci self.prange.update(touch.pressure) 130a46c0ec8Sopenharmony_ci 131a46c0ec8Sopenharmony_ci if touch.pressure < self.device.up: 132a46c0ec8Sopenharmony_ci self.is_down = False 133a46c0ec8Sopenharmony_ci elif touch.pressure > self.device.down: 134a46c0ec8Sopenharmony_ci self.is_down = True 135a46c0ec8Sopenharmony_ci self.was_down = True 136a46c0ec8Sopenharmony_ci 137a46c0ec8Sopenharmony_ci self.is_palm = touch.pressure > self.device.palm 138a46c0ec8Sopenharmony_ci if self.is_palm: 139a46c0ec8Sopenharmony_ci self.was_palm = True 140a46c0ec8Sopenharmony_ci 141a46c0ec8Sopenharmony_ci self.is_thumb = touch.pressure > self.device.thumb 142a46c0ec8Sopenharmony_ci if self.is_thumb: 143a46c0ec8Sopenharmony_ci self.was_thumb = True 144a46c0ec8Sopenharmony_ci 145a46c0ec8Sopenharmony_ci def finalize(self): 146a46c0ec8Sopenharmony_ci """Mark the TouchSequence as complete (finger is up)""" 147a46c0ec8Sopenharmony_ci self.is_active = False 148a46c0ec8Sopenharmony_ci 149a46c0ec8Sopenharmony_ci def avg(self): 150a46c0ec8Sopenharmony_ci """Average pressure value of this sequence""" 151a46c0ec8Sopenharmony_ci return int(sum([p.pressure for p in self.points]) / len(self.points)) 152a46c0ec8Sopenharmony_ci 153a46c0ec8Sopenharmony_ci def median(self): 154a46c0ec8Sopenharmony_ci """Median pressure value of this sequence""" 155a46c0ec8Sopenharmony_ci ps = sorted([p.pressure for p in self.points]) 156a46c0ec8Sopenharmony_ci idx = int(len(self.points) / 2) 157a46c0ec8Sopenharmony_ci return ps[idx] 158a46c0ec8Sopenharmony_ci 159a46c0ec8Sopenharmony_ci def __str__(self): 160a46c0ec8Sopenharmony_ci return self._str_state() if self.is_active else self._str_summary() 161a46c0ec8Sopenharmony_ci 162a46c0ec8Sopenharmony_ci def _str_summary(self): 163a46c0ec8Sopenharmony_ci if not self.points: 164a46c0ec8Sopenharmony_ci return fmt.values( 165a46c0ec8Sopenharmony_ci [ 166a46c0ec8Sopenharmony_ci self.tracking_id, 167a46c0ec8Sopenharmony_ci False, 168a46c0ec8Sopenharmony_ci False, 169a46c0ec8Sopenharmony_ci False, 170a46c0ec8Sopenharmony_ci False, 171a46c0ec8Sopenharmony_ci "No pressure values recorded", 172a46c0ec8Sopenharmony_ci ] 173a46c0ec8Sopenharmony_ci ) 174a46c0ec8Sopenharmony_ci 175a46c0ec8Sopenharmony_ci s = fmt.values( 176a46c0ec8Sopenharmony_ci [ 177a46c0ec8Sopenharmony_ci self.tracking_id, 178a46c0ec8Sopenharmony_ci self.was_down, 179a46c0ec8Sopenharmony_ci True, 180a46c0ec8Sopenharmony_ci self.was_palm, 181a46c0ec8Sopenharmony_ci self.was_thumb, 182a46c0ec8Sopenharmony_ci self.prange.min, 183a46c0ec8Sopenharmony_ci self.prange.max, 184a46c0ec8Sopenharmony_ci 0, 185a46c0ec8Sopenharmony_ci self.avg(), 186a46c0ec8Sopenharmony_ci self.median(), 187a46c0ec8Sopenharmony_ci ] 188a46c0ec8Sopenharmony_ci ) 189a46c0ec8Sopenharmony_ci 190a46c0ec8Sopenharmony_ci return s 191a46c0ec8Sopenharmony_ci 192a46c0ec8Sopenharmony_ci def _str_state(self): 193a46c0ec8Sopenharmony_ci s = fmt.values( 194a46c0ec8Sopenharmony_ci [ 195a46c0ec8Sopenharmony_ci self.tracking_id, 196a46c0ec8Sopenharmony_ci self.is_down, 197a46c0ec8Sopenharmony_ci not self.is_down, 198a46c0ec8Sopenharmony_ci self.is_palm, 199a46c0ec8Sopenharmony_ci self.is_thumb, 200a46c0ec8Sopenharmony_ci self.prange.min, 201a46c0ec8Sopenharmony_ci self.prange.max, 202a46c0ec8Sopenharmony_ci self.points[-1].pressure, 203a46c0ec8Sopenharmony_ci ] 204a46c0ec8Sopenharmony_ci ) 205a46c0ec8Sopenharmony_ci return s 206a46c0ec8Sopenharmony_ci 207a46c0ec8Sopenharmony_ci 208a46c0ec8Sopenharmony_ciclass InvalidDeviceError(Exception): 209a46c0ec8Sopenharmony_ci pass 210a46c0ec8Sopenharmony_ci 211a46c0ec8Sopenharmony_ci 212a46c0ec8Sopenharmony_ciclass Device(libevdev.Device): 213a46c0ec8Sopenharmony_ci def __init__(self, path): 214a46c0ec8Sopenharmony_ci if path is None: 215a46c0ec8Sopenharmony_ci self.path = self.find_touchpad_device() 216a46c0ec8Sopenharmony_ci else: 217a46c0ec8Sopenharmony_ci self.path = path 218a46c0ec8Sopenharmony_ci 219a46c0ec8Sopenharmony_ci fd = open(self.path, "rb") 220a46c0ec8Sopenharmony_ci super().__init__(fd) 221a46c0ec8Sopenharmony_ci 222a46c0ec8Sopenharmony_ci print("Using {}: {}\n".format(self.name, self.path)) 223a46c0ec8Sopenharmony_ci 224a46c0ec8Sopenharmony_ci self.has_mt_pressure = True 225a46c0ec8Sopenharmony_ci absinfo = self.absinfo[libevdev.EV_ABS.ABS_MT_PRESSURE] 226a46c0ec8Sopenharmony_ci if absinfo is None: 227a46c0ec8Sopenharmony_ci absinfo = self.absinfo[libevdev.EV_ABS.ABS_PRESSURE] 228a46c0ec8Sopenharmony_ci self.has_mt_pressure = False 229a46c0ec8Sopenharmony_ci if absinfo is None: 230a46c0ec8Sopenharmony_ci raise InvalidDeviceError( 231a46c0ec8Sopenharmony_ci "Device does not have ABS_PRESSURE or ABS_MT_PRESSURE" 232a46c0ec8Sopenharmony_ci ) 233a46c0ec8Sopenharmony_ci 234a46c0ec8Sopenharmony_ci prange = absinfo.maximum - absinfo.minimum 235a46c0ec8Sopenharmony_ci 236a46c0ec8Sopenharmony_ci # libinput defaults 237a46c0ec8Sopenharmony_ci self.down = int(absinfo.minimum + 0.12 * prange) 238a46c0ec8Sopenharmony_ci self.up = int(absinfo.minimum + 0.10 * prange) 239a46c0ec8Sopenharmony_ci self.palm = 130 # the libinput default 240a46c0ec8Sopenharmony_ci self.thumb = absinfo.maximum 241a46c0ec8Sopenharmony_ci 242a46c0ec8Sopenharmony_ci self._init_thresholds_from_quirks() 243a46c0ec8Sopenharmony_ci self.sequences = [] 244a46c0ec8Sopenharmony_ci 245a46c0ec8Sopenharmony_ci def find_touchpad_device(self): 246a46c0ec8Sopenharmony_ci context = pyudev.Context() 247a46c0ec8Sopenharmony_ci for device in context.list_devices(subsystem="input"): 248a46c0ec8Sopenharmony_ci if not device.get("ID_INPUT_TOUCHPAD", 0): 249a46c0ec8Sopenharmony_ci continue 250a46c0ec8Sopenharmony_ci 251a46c0ec8Sopenharmony_ci if not device.device_node or not device.device_node.startswith( 252a46c0ec8Sopenharmony_ci "/dev/input/event" 253a46c0ec8Sopenharmony_ci ): 254a46c0ec8Sopenharmony_ci continue 255a46c0ec8Sopenharmony_ci 256a46c0ec8Sopenharmony_ci return device.device_node 257a46c0ec8Sopenharmony_ci print("Unable to find a touchpad device.", file=sys.stderr) 258a46c0ec8Sopenharmony_ci sys.exit(1) 259a46c0ec8Sopenharmony_ci 260a46c0ec8Sopenharmony_ci def _init_thresholds_from_quirks(self): 261a46c0ec8Sopenharmony_ci command = ["libinput", "quirks", "list", self.path] 262a46c0ec8Sopenharmony_ci cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 263a46c0ec8Sopenharmony_ci if cmd.returncode != 0: 264a46c0ec8Sopenharmony_ci print( 265a46c0ec8Sopenharmony_ci "Error querying quirks: {}".format(cmd.stderr.decode("utf-8")), 266a46c0ec8Sopenharmony_ci file=sys.stderr, 267a46c0ec8Sopenharmony_ci ) 268a46c0ec8Sopenharmony_ci return 269a46c0ec8Sopenharmony_ci 270a46c0ec8Sopenharmony_ci stdout = cmd.stdout.decode("utf-8") 271a46c0ec8Sopenharmony_ci quirks = [q.split("=") for q in stdout.split("\n")] 272a46c0ec8Sopenharmony_ci 273a46c0ec8Sopenharmony_ci for q in quirks: 274a46c0ec8Sopenharmony_ci if q[0] == "AttrPalmPressureThreshold": 275a46c0ec8Sopenharmony_ci self.palm = int(q[1]) 276a46c0ec8Sopenharmony_ci elif q[0] == "AttrPressureRange": 277a46c0ec8Sopenharmony_ci self.down, self.up = colon_tuple(q[1]) 278a46c0ec8Sopenharmony_ci elif q[0] == "AttrThumbPressureThreshold": 279a46c0ec8Sopenharmony_ci self.thumb = int(q[1]) 280a46c0ec8Sopenharmony_ci 281a46c0ec8Sopenharmony_ci def start_new_sequence(self, tracking_id): 282a46c0ec8Sopenharmony_ci self.sequences.append(TouchSequence(self, tracking_id)) 283a46c0ec8Sopenharmony_ci 284a46c0ec8Sopenharmony_ci def current_sequence(self): 285a46c0ec8Sopenharmony_ci return self.sequences[-1] 286a46c0ec8Sopenharmony_ci 287a46c0ec8Sopenharmony_ci 288a46c0ec8Sopenharmony_cidef handle_key(device, event): 289a46c0ec8Sopenharmony_ci tapcodes = [ 290a46c0ec8Sopenharmony_ci libevdev.EV_KEY.BTN_TOOL_DOUBLETAP, 291a46c0ec8Sopenharmony_ci libevdev.EV_KEY.BTN_TOOL_TRIPLETAP, 292a46c0ec8Sopenharmony_ci libevdev.EV_KEY.BTN_TOOL_QUADTAP, 293a46c0ec8Sopenharmony_ci libevdev.EV_KEY.BTN_TOOL_QUINTTAP, 294a46c0ec8Sopenharmony_ci ] 295a46c0ec8Sopenharmony_ci if event.code in tapcodes and event.value > 0: 296a46c0ec8Sopenharmony_ci print( 297a46c0ec8Sopenharmony_ci "\r\033[2KThis tool cannot handle multiple fingers, " 298a46c0ec8Sopenharmony_ci "output will be invalid" 299a46c0ec8Sopenharmony_ci ) 300a46c0ec8Sopenharmony_ci 301a46c0ec8Sopenharmony_ci 302a46c0ec8Sopenharmony_cidef handle_abs(device, event): 303a46c0ec8Sopenharmony_ci if event.matches(libevdev.EV_ABS.ABS_MT_TRACKING_ID): 304a46c0ec8Sopenharmony_ci if event.value > -1: 305a46c0ec8Sopenharmony_ci device.start_new_sequence(event.value) 306a46c0ec8Sopenharmony_ci else: 307a46c0ec8Sopenharmony_ci try: 308a46c0ec8Sopenharmony_ci s = device.current_sequence() 309a46c0ec8Sopenharmony_ci s.finalize() 310a46c0ec8Sopenharmony_ci print("\r\033[2K{}".format(s)) 311a46c0ec8Sopenharmony_ci except IndexError: 312a46c0ec8Sopenharmony_ci # If the finger was down at startup 313a46c0ec8Sopenharmony_ci pass 314a46c0ec8Sopenharmony_ci elif event.matches(libevdev.EV_ABS.ABS_MT_PRESSURE) or ( 315a46c0ec8Sopenharmony_ci event.matches(libevdev.EV_ABS.ABS_PRESSURE) and not device.has_mt_pressure 316a46c0ec8Sopenharmony_ci ): 317a46c0ec8Sopenharmony_ci try: 318a46c0ec8Sopenharmony_ci s = device.current_sequence() 319a46c0ec8Sopenharmony_ci s.append(Touch(pressure=event.value)) 320a46c0ec8Sopenharmony_ci print("\r\033[2K{}".format(s), end="") 321a46c0ec8Sopenharmony_ci except IndexError: 322a46c0ec8Sopenharmony_ci # If the finger was down at startup 323a46c0ec8Sopenharmony_ci pass 324a46c0ec8Sopenharmony_ci 325a46c0ec8Sopenharmony_ci 326a46c0ec8Sopenharmony_cidef handle_event(device, event): 327a46c0ec8Sopenharmony_ci if event.matches(libevdev.EV_ABS): 328a46c0ec8Sopenharmony_ci handle_abs(device, event) 329a46c0ec8Sopenharmony_ci elif event.matches(libevdev.EV_KEY): 330a46c0ec8Sopenharmony_ci handle_key(device, event) 331a46c0ec8Sopenharmony_ci 332a46c0ec8Sopenharmony_ci 333a46c0ec8Sopenharmony_cidef loop(device): 334a46c0ec8Sopenharmony_ci print("This is an interactive tool") 335a46c0ec8Sopenharmony_ci print() 336a46c0ec8Sopenharmony_ci print("Place a single finger on the touchpad to measure pressure values.") 337a46c0ec8Sopenharmony_ci print("Check that:") 338a46c0ec8Sopenharmony_ci print("- touches subjectively perceived as down are tagged as down") 339a46c0ec8Sopenharmony_ci print("- touches with a thumb are tagged as thumb") 340a46c0ec8Sopenharmony_ci print("- touches with a palm are tagged as palm") 341a46c0ec8Sopenharmony_ci print() 342a46c0ec8Sopenharmony_ci print("If the touch states do not match the interaction, re-run") 343a46c0ec8Sopenharmony_ci print("with --touch-thresholds=down:up using observed pressure values.") 344a46c0ec8Sopenharmony_ci print("See --help for more options.") 345a46c0ec8Sopenharmony_ci print() 346a46c0ec8Sopenharmony_ci print("Press Ctrl+C to exit") 347a46c0ec8Sopenharmony_ci print() 348a46c0ec8Sopenharmony_ci 349a46c0ec8Sopenharmony_ci headers = fmt.headers( 350a46c0ec8Sopenharmony_ci ["Touch", "down", "up", "palm", "thumb", "min", "max", "p", "avg", "median"] 351a46c0ec8Sopenharmony_ci ) 352a46c0ec8Sopenharmony_ci print(fmt.separator()) 353a46c0ec8Sopenharmony_ci print(fmt.values(["Thresh", device.down, device.up, device.palm, device.thumb])) 354a46c0ec8Sopenharmony_ci print(fmt.separator()) 355a46c0ec8Sopenharmony_ci print(headers) 356a46c0ec8Sopenharmony_ci print(fmt.separator()) 357a46c0ec8Sopenharmony_ci 358a46c0ec8Sopenharmony_ci while True: 359a46c0ec8Sopenharmony_ci for event in device.events(): 360a46c0ec8Sopenharmony_ci handle_event(device, event) 361a46c0ec8Sopenharmony_ci 362a46c0ec8Sopenharmony_ci 363a46c0ec8Sopenharmony_cidef colon_tuple(string): 364a46c0ec8Sopenharmony_ci try: 365a46c0ec8Sopenharmony_ci ts = string.split(":") 366a46c0ec8Sopenharmony_ci t = tuple([int(x) for x in ts]) 367a46c0ec8Sopenharmony_ci if len(t) == 2 and t[0] >= t[1]: 368a46c0ec8Sopenharmony_ci return t 369a46c0ec8Sopenharmony_ci except: # noqa 370a46c0ec8Sopenharmony_ci pass 371a46c0ec8Sopenharmony_ci 372a46c0ec8Sopenharmony_ci msg = "{} is not in format N:M (N >= M)".format(string) 373a46c0ec8Sopenharmony_ci raise argparse.ArgumentTypeError(msg) 374a46c0ec8Sopenharmony_ci 375a46c0ec8Sopenharmony_ci 376a46c0ec8Sopenharmony_cidef main(args): 377a46c0ec8Sopenharmony_ci parser = argparse.ArgumentParser(description="Measure touchpad pressure values") 378a46c0ec8Sopenharmony_ci parser.add_argument( 379a46c0ec8Sopenharmony_ci "path", 380a46c0ec8Sopenharmony_ci metavar="/dev/input/event0", 381a46c0ec8Sopenharmony_ci nargs="?", 382a46c0ec8Sopenharmony_ci type=str, 383a46c0ec8Sopenharmony_ci help="Path to device (optional)", 384a46c0ec8Sopenharmony_ci ) 385a46c0ec8Sopenharmony_ci parser.add_argument( 386a46c0ec8Sopenharmony_ci "--touch-thresholds", 387a46c0ec8Sopenharmony_ci metavar="down:up", 388a46c0ec8Sopenharmony_ci type=colon_tuple, 389a46c0ec8Sopenharmony_ci help="Thresholds when a touch is logically down or up", 390a46c0ec8Sopenharmony_ci ) 391a46c0ec8Sopenharmony_ci parser.add_argument( 392a46c0ec8Sopenharmony_ci "--palm-threshold", 393a46c0ec8Sopenharmony_ci metavar="t", 394a46c0ec8Sopenharmony_ci type=int, 395a46c0ec8Sopenharmony_ci help="Threshold when a touch is a palm", 396a46c0ec8Sopenharmony_ci ) 397a46c0ec8Sopenharmony_ci parser.add_argument( 398a46c0ec8Sopenharmony_ci "--thumb-threshold", 399a46c0ec8Sopenharmony_ci metavar="t", 400a46c0ec8Sopenharmony_ci type=int, 401a46c0ec8Sopenharmony_ci help="Threshold when a touch is a thumb", 402a46c0ec8Sopenharmony_ci ) 403a46c0ec8Sopenharmony_ci args = parser.parse_args() 404a46c0ec8Sopenharmony_ci 405a46c0ec8Sopenharmony_ci try: 406a46c0ec8Sopenharmony_ci device = Device(args.path) 407a46c0ec8Sopenharmony_ci 408a46c0ec8Sopenharmony_ci if args.touch_thresholds is not None: 409a46c0ec8Sopenharmony_ci device.down, device.up = args.touch_thresholds 410a46c0ec8Sopenharmony_ci 411a46c0ec8Sopenharmony_ci if args.palm_threshold is not None: 412a46c0ec8Sopenharmony_ci device.palm = args.palm_threshold 413a46c0ec8Sopenharmony_ci 414a46c0ec8Sopenharmony_ci if args.thumb_threshold is not None: 415a46c0ec8Sopenharmony_ci device.thumb = args.thumb_threshold 416a46c0ec8Sopenharmony_ci 417a46c0ec8Sopenharmony_ci loop(device) 418a46c0ec8Sopenharmony_ci except KeyboardInterrupt: 419a46c0ec8Sopenharmony_ci print("\r\033[2K{}".format(fmt.separator())) 420a46c0ec8Sopenharmony_ci print() 421a46c0ec8Sopenharmony_ci 422a46c0ec8Sopenharmony_ci except (PermissionError, OSError): 423a46c0ec8Sopenharmony_ci print("Error: failed to open device") 424a46c0ec8Sopenharmony_ci except InvalidDeviceError as e: 425a46c0ec8Sopenharmony_ci print( 426a46c0ec8Sopenharmony_ci "This device does not have the capabilities for pressure-based touch detection." 427a46c0ec8Sopenharmony_ci ) 428a46c0ec8Sopenharmony_ci print("Details: {}".format(e)) 429a46c0ec8Sopenharmony_ci 430a46c0ec8Sopenharmony_ci 431a46c0ec8Sopenharmony_ciif __name__ == "__main__": 432a46c0ec8Sopenharmony_ci main(sys.argv) 433