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 © 2018 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 os 28a46c0ec8Sopenharmony_ciimport sys 29a46c0ec8Sopenharmony_ciimport argparse 30a46c0ec8Sopenharmony_ciimport subprocess 31a46c0ec8Sopenharmony_ci 32a46c0ec8Sopenharmony_citry: 33a46c0ec8Sopenharmony_ci import libevdev 34a46c0ec8Sopenharmony_ci import pyudev 35a46c0ec8Sopenharmony_ciexcept ModuleNotFoundError as e: 36a46c0ec8Sopenharmony_ci print("Error: {}".format(str(e)), file=sys.stderr) 37a46c0ec8Sopenharmony_ci print( 38a46c0ec8Sopenharmony_ci "One or more python modules are missing. Please install those " 39a46c0ec8Sopenharmony_ci "modules and re-run this tool." 40a46c0ec8Sopenharmony_ci ) 41a46c0ec8Sopenharmony_ci sys.exit(1) 42a46c0ec8Sopenharmony_ci 43a46c0ec8Sopenharmony_ci 44a46c0ec8Sopenharmony_ciDEFAULT_HWDB_FILE = "/usr/lib/udev/hwdb.d/60-evdev.hwdb" 45a46c0ec8Sopenharmony_ciOVERRIDE_HWDB_FILE = "/etc/udev/hwdb.d/99-touchpad-fuzz-override.hwdb" 46a46c0ec8Sopenharmony_ci 47a46c0ec8Sopenharmony_ci 48a46c0ec8Sopenharmony_ciclass tcolors: 49a46c0ec8Sopenharmony_ci GREEN = "\033[92m" 50a46c0ec8Sopenharmony_ci RED = "\033[91m" 51a46c0ec8Sopenharmony_ci YELLOW = "\033[93m" 52a46c0ec8Sopenharmony_ci BOLD = "\033[1m" 53a46c0ec8Sopenharmony_ci NORMAL = "\033[0m" 54a46c0ec8Sopenharmony_ci 55a46c0ec8Sopenharmony_ci 56a46c0ec8Sopenharmony_cidef print_bold(msg, **kwargs): 57a46c0ec8Sopenharmony_ci print(tcolors.BOLD + msg + tcolors.NORMAL, **kwargs) 58a46c0ec8Sopenharmony_ci 59a46c0ec8Sopenharmony_ci 60a46c0ec8Sopenharmony_cidef print_green(msg, **kwargs): 61a46c0ec8Sopenharmony_ci print(tcolors.BOLD + tcolors.GREEN + msg + tcolors.NORMAL, **kwargs) 62a46c0ec8Sopenharmony_ci 63a46c0ec8Sopenharmony_ci 64a46c0ec8Sopenharmony_cidef print_yellow(msg, **kwargs): 65a46c0ec8Sopenharmony_ci print(tcolors.BOLD + tcolors.YELLOW + msg + tcolors.NORMAL, **kwargs) 66a46c0ec8Sopenharmony_ci 67a46c0ec8Sopenharmony_ci 68a46c0ec8Sopenharmony_cidef print_red(msg, **kwargs): 69a46c0ec8Sopenharmony_ci print(tcolors.BOLD + tcolors.RED + msg + tcolors.NORMAL, **kwargs) 70a46c0ec8Sopenharmony_ci 71a46c0ec8Sopenharmony_ci 72a46c0ec8Sopenharmony_ciclass InvalidConfigurationError(Exception): 73a46c0ec8Sopenharmony_ci pass 74a46c0ec8Sopenharmony_ci 75a46c0ec8Sopenharmony_ci 76a46c0ec8Sopenharmony_ciclass InvalidDeviceError(Exception): 77a46c0ec8Sopenharmony_ci pass 78a46c0ec8Sopenharmony_ci 79a46c0ec8Sopenharmony_ci 80a46c0ec8Sopenharmony_ciclass Device(libevdev.Device): 81a46c0ec8Sopenharmony_ci def __init__(self, path): 82a46c0ec8Sopenharmony_ci if path is None: 83a46c0ec8Sopenharmony_ci self.path = self.find_touch_device() 84a46c0ec8Sopenharmony_ci else: 85a46c0ec8Sopenharmony_ci self.path = path 86a46c0ec8Sopenharmony_ci 87a46c0ec8Sopenharmony_ci fd = open(self.path, "rb") 88a46c0ec8Sopenharmony_ci super().__init__(fd) 89a46c0ec8Sopenharmony_ci context = pyudev.Context() 90a46c0ec8Sopenharmony_ci self.udev_device = pyudev.Devices.from_device_file(context, self.path) 91a46c0ec8Sopenharmony_ci 92a46c0ec8Sopenharmony_ci def find_touch_device(self): 93a46c0ec8Sopenharmony_ci context = pyudev.Context() 94a46c0ec8Sopenharmony_ci for device in context.list_devices(subsystem="input"): 95a46c0ec8Sopenharmony_ci if not device.get("ID_INPUT_TOUCHPAD", 0): 96a46c0ec8Sopenharmony_ci continue 97a46c0ec8Sopenharmony_ci 98a46c0ec8Sopenharmony_ci if not device.device_node or not device.device_node.startswith( 99a46c0ec8Sopenharmony_ci "/dev/input/event" 100a46c0ec8Sopenharmony_ci ): 101a46c0ec8Sopenharmony_ci continue 102a46c0ec8Sopenharmony_ci 103a46c0ec8Sopenharmony_ci return device.device_node 104a46c0ec8Sopenharmony_ci 105a46c0ec8Sopenharmony_ci print("Unable to find a touch device.", file=sys.stderr) 106a46c0ec8Sopenharmony_ci sys.exit(1) 107a46c0ec8Sopenharmony_ci 108a46c0ec8Sopenharmony_ci def check_property(self): 109a46c0ec8Sopenharmony_ci """Return a tuple of (xfuzz, yfuzz) with the fuzz as set in the libinput 110a46c0ec8Sopenharmony_ci property. Returns None if the property doesn't exist""" 111a46c0ec8Sopenharmony_ci 112a46c0ec8Sopenharmony_ci axes = { 113a46c0ec8Sopenharmony_ci 0x00: self.udev_device.get("LIBINPUT_FUZZ_00"), 114a46c0ec8Sopenharmony_ci 0x01: self.udev_device.get("LIBINPUT_FUZZ_01"), 115a46c0ec8Sopenharmony_ci 0x35: self.udev_device.get("LIBINPUT_FUZZ_35"), 116a46c0ec8Sopenharmony_ci 0x36: self.udev_device.get("LIBINPUT_FUZZ_36"), 117a46c0ec8Sopenharmony_ci } 118a46c0ec8Sopenharmony_ci 119a46c0ec8Sopenharmony_ci if axes[0x35] is not None: 120a46c0ec8Sopenharmony_ci if axes[0x35] != axes[0x00]: 121a46c0ec8Sopenharmony_ci print_bold( 122a46c0ec8Sopenharmony_ci "WARNING: fuzz mismatch ABS_X: {}, ABS_MT_POSITION_X: {}".format( 123a46c0ec8Sopenharmony_ci axes[0x00], axes[0x35] 124a46c0ec8Sopenharmony_ci ) 125a46c0ec8Sopenharmony_ci ) 126a46c0ec8Sopenharmony_ci 127a46c0ec8Sopenharmony_ci if axes[0x36] is not None: 128a46c0ec8Sopenharmony_ci if axes[0x36] != axes[0x01]: 129a46c0ec8Sopenharmony_ci print_bold( 130a46c0ec8Sopenharmony_ci "WARNING: fuzz mismatch ABS_Y: {}, ABS_MT_POSITION_Y: {}".format( 131a46c0ec8Sopenharmony_ci axes[0x01], axes[0x36] 132a46c0ec8Sopenharmony_ci ) 133a46c0ec8Sopenharmony_ci ) 134a46c0ec8Sopenharmony_ci 135a46c0ec8Sopenharmony_ci xfuzz = axes[0x35] or axes[0x00] 136a46c0ec8Sopenharmony_ci yfuzz = axes[0x36] or axes[0x01] 137a46c0ec8Sopenharmony_ci 138a46c0ec8Sopenharmony_ci if xfuzz is None and yfuzz is None: 139a46c0ec8Sopenharmony_ci return None 140a46c0ec8Sopenharmony_ci 141a46c0ec8Sopenharmony_ci if (xfuzz is not None and yfuzz is None) or ( 142a46c0ec8Sopenharmony_ci xfuzz is None and yfuzz is not None 143a46c0ec8Sopenharmony_ci ): 144a46c0ec8Sopenharmony_ci raise InvalidConfigurationError("fuzz should be set for both axes") 145a46c0ec8Sopenharmony_ci 146a46c0ec8Sopenharmony_ci return (int(xfuzz), int(yfuzz)) 147a46c0ec8Sopenharmony_ci 148a46c0ec8Sopenharmony_ci def check_axes(self): 149a46c0ec8Sopenharmony_ci """ 150a46c0ec8Sopenharmony_ci Returns a tuple of (xfuzz, yfuzz) with the fuzz as set on the device 151a46c0ec8Sopenharmony_ci axis. Returns None if no fuzz is set. 152a46c0ec8Sopenharmony_ci """ 153a46c0ec8Sopenharmony_ci if not self.has(libevdev.EV_ABS.ABS_X) or not self.has(libevdev.EV_ABS.ABS_Y): 154a46c0ec8Sopenharmony_ci raise InvalidDeviceError("device does not have x/y axes") 155a46c0ec8Sopenharmony_ci 156a46c0ec8Sopenharmony_ci if self.has(libevdev.EV_ABS.ABS_MT_POSITION_X) != self.has( 157a46c0ec8Sopenharmony_ci libevdev.EV_ABS.ABS_MT_POSITION_Y 158a46c0ec8Sopenharmony_ci ): 159a46c0ec8Sopenharmony_ci raise InvalidDeviceError("device does not have both multitouch axes") 160a46c0ec8Sopenharmony_ci 161a46c0ec8Sopenharmony_ci xfuzz = ( 162a46c0ec8Sopenharmony_ci self.absinfo[libevdev.EV_ABS.ABS_X].fuzz 163a46c0ec8Sopenharmony_ci or self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X].fuzz 164a46c0ec8Sopenharmony_ci ) 165a46c0ec8Sopenharmony_ci yfuzz = ( 166a46c0ec8Sopenharmony_ci self.absinfo[libevdev.EV_ABS.ABS_Y].fuzz 167a46c0ec8Sopenharmony_ci or self.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_Y].fuzz 168a46c0ec8Sopenharmony_ci ) 169a46c0ec8Sopenharmony_ci 170a46c0ec8Sopenharmony_ci if xfuzz == 0 and yfuzz == 0: 171a46c0ec8Sopenharmony_ci return None 172a46c0ec8Sopenharmony_ci 173a46c0ec8Sopenharmony_ci return (xfuzz, yfuzz) 174a46c0ec8Sopenharmony_ci 175a46c0ec8Sopenharmony_ci 176a46c0ec8Sopenharmony_cidef print_fuzz(what, fuzz): 177a46c0ec8Sopenharmony_ci print(" Checking {}... ".format(what), end="") 178a46c0ec8Sopenharmony_ci if fuzz is None: 179a46c0ec8Sopenharmony_ci print("not set") 180a46c0ec8Sopenharmony_ci elif fuzz == (0, 0): 181a46c0ec8Sopenharmony_ci print("is zero") 182a46c0ec8Sopenharmony_ci else: 183a46c0ec8Sopenharmony_ci print("x={} y={}".format(*fuzz)) 184a46c0ec8Sopenharmony_ci 185a46c0ec8Sopenharmony_ci 186a46c0ec8Sopenharmony_cidef handle_existing_entry(device, fuzz): 187a46c0ec8Sopenharmony_ci # This is getting messy because we don't really know where the entry 188a46c0ec8Sopenharmony_ci # could be or how the match rule looks like. So we just check the 189a46c0ec8Sopenharmony_ci # default location only. 190a46c0ec8Sopenharmony_ci # For the match comparison, we search for the property value in the 191a46c0ec8Sopenharmony_ci # file. If there is more than one entry that uses the same 192a46c0ec8Sopenharmony_ci # overrides this will generate false positives. 193a46c0ec8Sopenharmony_ci # If the lines aren't in the same order in the file, it'll be a false 194a46c0ec8Sopenharmony_ci # negative. 195a46c0ec8Sopenharmony_ci overrides = { 196a46c0ec8Sopenharmony_ci 0x00: device.udev_device.get("EVDEV_ABS_00"), 197a46c0ec8Sopenharmony_ci 0x01: device.udev_device.get("EVDEV_ABS_01"), 198a46c0ec8Sopenharmony_ci 0x35: device.udev_device.get("EVDEV_ABS_35"), 199a46c0ec8Sopenharmony_ci 0x36: device.udev_device.get("EVDEV_ABS_36"), 200a46c0ec8Sopenharmony_ci } 201a46c0ec8Sopenharmony_ci 202a46c0ec8Sopenharmony_ci has_existing_rules = False 203a46c0ec8Sopenharmony_ci for key, value in overrides.items(): 204a46c0ec8Sopenharmony_ci if value is not None: 205a46c0ec8Sopenharmony_ci has_existing_rules = True 206a46c0ec8Sopenharmony_ci break 207a46c0ec8Sopenharmony_ci if not has_existing_rules: 208a46c0ec8Sopenharmony_ci return False 209a46c0ec8Sopenharmony_ci 210a46c0ec8Sopenharmony_ci print_red("Error! ", end="") 211a46c0ec8Sopenharmony_ci print("This device already has axis overrides defined") 212a46c0ec8Sopenharmony_ci print("") 213a46c0ec8Sopenharmony_ci print_bold("Searching for existing override...") 214a46c0ec8Sopenharmony_ci 215a46c0ec8Sopenharmony_ci # Construct a template that looks like a hwdb entry (values only) from 216a46c0ec8Sopenharmony_ci # the udev property values 217a46c0ec8Sopenharmony_ci template = [ 218a46c0ec8Sopenharmony_ci " EVDEV_ABS_00={}".format(overrides[0x00]), 219a46c0ec8Sopenharmony_ci " EVDEV_ABS_01={}".format(overrides[0x01]), 220a46c0ec8Sopenharmony_ci ] 221a46c0ec8Sopenharmony_ci if overrides[0x35] is not None: 222a46c0ec8Sopenharmony_ci template += [ 223a46c0ec8Sopenharmony_ci " EVDEV_ABS_35={}".format(overrides[0x35]), 224a46c0ec8Sopenharmony_ci " EVDEV_ABS_36={}".format(overrides[0x36]), 225a46c0ec8Sopenharmony_ci ] 226a46c0ec8Sopenharmony_ci 227a46c0ec8Sopenharmony_ci print("Checking in {}... ".format(OVERRIDE_HWDB_FILE), end="") 228a46c0ec8Sopenharmony_ci entry, prefix, lineno = check_file_for_lines(OVERRIDE_HWDB_FILE, template) 229a46c0ec8Sopenharmony_ci if entry is not None: 230a46c0ec8Sopenharmony_ci print_green("found") 231a46c0ec8Sopenharmony_ci print("The existing hwdb entry can be overwritten") 232a46c0ec8Sopenharmony_ci return False 233a46c0ec8Sopenharmony_ci else: 234a46c0ec8Sopenharmony_ci print_red("not found") 235a46c0ec8Sopenharmony_ci print("Checking in {}... ".format(DEFAULT_HWDB_FILE), end="") 236a46c0ec8Sopenharmony_ci entry, prefix, lineno = check_file_for_lines(DEFAULT_HWDB_FILE, template) 237a46c0ec8Sopenharmony_ci if entry is not None: 238a46c0ec8Sopenharmony_ci print_green("found") 239a46c0ec8Sopenharmony_ci else: 240a46c0ec8Sopenharmony_ci print_red("not found") 241a46c0ec8Sopenharmony_ci print( 242a46c0ec8Sopenharmony_ci "The device has a hwdb override defined but it's not where I expected it to be." 243a46c0ec8Sopenharmony_ci ) 244a46c0ec8Sopenharmony_ci print("Please look at the libinput documentation for more details.") 245a46c0ec8Sopenharmony_ci print("Exiting now.") 246a46c0ec8Sopenharmony_ci return True 247a46c0ec8Sopenharmony_ci 248a46c0ec8Sopenharmony_ci print_bold("Probable entry for this device found in line {}:".format(lineno)) 249a46c0ec8Sopenharmony_ci print("\n".join(prefix + entry)) 250a46c0ec8Sopenharmony_ci print("") 251a46c0ec8Sopenharmony_ci 252a46c0ec8Sopenharmony_ci print_bold("Suggested new entry for this device:") 253a46c0ec8Sopenharmony_ci new_entry = [] 254a46c0ec8Sopenharmony_ci for i in range(0, len(template)): 255a46c0ec8Sopenharmony_ci parts = entry[i].split(":") 256a46c0ec8Sopenharmony_ci while len(parts) < 4: 257a46c0ec8Sopenharmony_ci parts.append("") 258a46c0ec8Sopenharmony_ci parts[3] = str(fuzz) 259a46c0ec8Sopenharmony_ci new_entry.append(":".join(parts)) 260a46c0ec8Sopenharmony_ci print("\n".join(prefix + new_entry)) 261a46c0ec8Sopenharmony_ci print("") 262a46c0ec8Sopenharmony_ci 263a46c0ec8Sopenharmony_ci # Not going to overwrite the 60-evdev.hwdb entry with this program, too 264a46c0ec8Sopenharmony_ci # risky. And it may not be our device match anyway. 265a46c0ec8Sopenharmony_ci print_bold("You must now:") 266a46c0ec8Sopenharmony_ci print( 267a46c0ec8Sopenharmony_ci "\n".join( 268a46c0ec8Sopenharmony_ci ( 269a46c0ec8Sopenharmony_ci "1. Check the above suggestion for sanity. Does it match your device?", 270a46c0ec8Sopenharmony_ci "2. Open {} and amend the existing entry".format(DEFAULT_HWDB_FILE), 271a46c0ec8Sopenharmony_ci " as recommended above", 272a46c0ec8Sopenharmony_ci "", 273a46c0ec8Sopenharmony_ci " The property format is:", 274a46c0ec8Sopenharmony_ci " EVDEV_ABS_00=min:max:resolution:fuzz", 275a46c0ec8Sopenharmony_ci "", 276a46c0ec8Sopenharmony_ci " Leave the entry as-is and only add or amend the fuzz value.", 277a46c0ec8Sopenharmony_ci " A non-existent value can be skipped, e.g. this entry sets the ", 278a46c0ec8Sopenharmony_ci " resolution to 32 and the fuzz to 8", 279a46c0ec8Sopenharmony_ci " EVDEV_ABS_00=::32:8", 280a46c0ec8Sopenharmony_ci "", 281a46c0ec8Sopenharmony_ci "3. Save the edited file", 282a46c0ec8Sopenharmony_ci "4. Say Y to the next prompt", 283a46c0ec8Sopenharmony_ci ) 284a46c0ec8Sopenharmony_ci ) 285a46c0ec8Sopenharmony_ci ) 286a46c0ec8Sopenharmony_ci 287a46c0ec8Sopenharmony_ci cont = input("Continue? [Y/n] ") 288a46c0ec8Sopenharmony_ci if cont == "n": 289a46c0ec8Sopenharmony_ci raise KeyboardInterrupt 290a46c0ec8Sopenharmony_ci 291a46c0ec8Sopenharmony_ci if test_hwdb_entry(device, fuzz): 292a46c0ec8Sopenharmony_ci print_bold("Please test the new fuzz setting by restarting libinput") 293a46c0ec8Sopenharmony_ci print_bold( 294a46c0ec8Sopenharmony_ci "Then submit a pull request for this hwdb entry change to " 295a46c0ec8Sopenharmony_ci "to systemd at http://github.com/systemd/systemd" 296a46c0ec8Sopenharmony_ci ) 297a46c0ec8Sopenharmony_ci else: 298a46c0ec8Sopenharmony_ci print_bold("The new fuzz setting did not take effect.") 299a46c0ec8Sopenharmony_ci print_bold("Did you edit the correct file?") 300a46c0ec8Sopenharmony_ci print("Please look at the libinput documentation for more details.") 301a46c0ec8Sopenharmony_ci print("Exiting now.") 302a46c0ec8Sopenharmony_ci 303a46c0ec8Sopenharmony_ci return True 304a46c0ec8Sopenharmony_ci 305a46c0ec8Sopenharmony_ci 306a46c0ec8Sopenharmony_cidef reload_and_trigger_udev(device): 307a46c0ec8Sopenharmony_ci import time 308a46c0ec8Sopenharmony_ci 309a46c0ec8Sopenharmony_ci print("Running systemd-hwdb update") 310a46c0ec8Sopenharmony_ci subprocess.run(["systemd-hwdb", "update"], check=True) 311a46c0ec8Sopenharmony_ci syspath = device.path.replace("/dev/input/", "/sys/class/input/") 312a46c0ec8Sopenharmony_ci time.sleep(2) 313a46c0ec8Sopenharmony_ci print("Running udevadm trigger {}".format(syspath)) 314a46c0ec8Sopenharmony_ci subprocess.run(["udevadm", "trigger", syspath], check=True) 315a46c0ec8Sopenharmony_ci time.sleep(2) 316a46c0ec8Sopenharmony_ci 317a46c0ec8Sopenharmony_ci 318a46c0ec8Sopenharmony_cidef test_hwdb_entry(device, fuzz): 319a46c0ec8Sopenharmony_ci reload_and_trigger_udev(device) 320a46c0ec8Sopenharmony_ci print_bold("Testing... ", end="") 321a46c0ec8Sopenharmony_ci 322a46c0ec8Sopenharmony_ci d = Device(device.path) 323a46c0ec8Sopenharmony_ci f = d.check_axes() 324a46c0ec8Sopenharmony_ci if f is not None: 325a46c0ec8Sopenharmony_ci if f == (fuzz, fuzz): 326a46c0ec8Sopenharmony_ci print_yellow("Warning") 327a46c0ec8Sopenharmony_ci print_bold( 328a46c0ec8Sopenharmony_ci "The hwdb applied to the device but libinput's udev " 329a46c0ec8Sopenharmony_ci "rules have not picked it up. This should only happen" 330a46c0ec8Sopenharmony_ci "if libinput is not installed" 331a46c0ec8Sopenharmony_ci ) 332a46c0ec8Sopenharmony_ci return True 333a46c0ec8Sopenharmony_ci else: 334a46c0ec8Sopenharmony_ci print_red("Error") 335a46c0ec8Sopenharmony_ci return False 336a46c0ec8Sopenharmony_ci else: 337a46c0ec8Sopenharmony_ci f = d.check_property() 338a46c0ec8Sopenharmony_ci if f is not None and f == (fuzz, fuzz): 339a46c0ec8Sopenharmony_ci print_green("Success") 340a46c0ec8Sopenharmony_ci return True 341a46c0ec8Sopenharmony_ci else: 342a46c0ec8Sopenharmony_ci print_red("Error") 343a46c0ec8Sopenharmony_ci return False 344a46c0ec8Sopenharmony_ci 345a46c0ec8Sopenharmony_ci 346a46c0ec8Sopenharmony_cidef check_file_for_lines(path, template): 347a46c0ec8Sopenharmony_ci """ 348a46c0ec8Sopenharmony_ci Checks file at path for the lines given in template. If found, the 349a46c0ec8Sopenharmony_ci return value is a tuple of the matching lines and the prefix (i.e. the 350a46c0ec8Sopenharmony_ci two lines before the matching lines) 351a46c0ec8Sopenharmony_ci """ 352a46c0ec8Sopenharmony_ci try: 353a46c0ec8Sopenharmony_ci lines = [l[:-1] for l in open(path).readlines()] 354a46c0ec8Sopenharmony_ci idx = -1 355a46c0ec8Sopenharmony_ci try: 356a46c0ec8Sopenharmony_ci while idx < len(lines) - 1: 357a46c0ec8Sopenharmony_ci idx += 1 358a46c0ec8Sopenharmony_ci line = lines[idx] 359a46c0ec8Sopenharmony_ci if not line.startswith(" EVDEV_ABS_00"): 360a46c0ec8Sopenharmony_ci continue 361a46c0ec8Sopenharmony_ci if lines[idx : idx + len(template)] != template: 362a46c0ec8Sopenharmony_ci continue 363a46c0ec8Sopenharmony_ci 364a46c0ec8Sopenharmony_ci return (lines[idx : idx + len(template)], lines[idx - 2 : idx], idx) 365a46c0ec8Sopenharmony_ci 366a46c0ec8Sopenharmony_ci except IndexError: 367a46c0ec8Sopenharmony_ci pass 368a46c0ec8Sopenharmony_ci except FileNotFoundError: 369a46c0ec8Sopenharmony_ci pass 370a46c0ec8Sopenharmony_ci 371a46c0ec8Sopenharmony_ci return (None, None, None) 372a46c0ec8Sopenharmony_ci 373a46c0ec8Sopenharmony_ci 374a46c0ec8Sopenharmony_cidef write_udev_rule(device, fuzz): 375a46c0ec8Sopenharmony_ci """Write out a udev rule that may match the device, run udevadm trigger and 376a46c0ec8Sopenharmony_ci check if the udev rule worked. Of course, there's plenty to go wrong... 377a46c0ec8Sopenharmony_ci """ 378a46c0ec8Sopenharmony_ci print("") 379a46c0ec8Sopenharmony_ci print_bold("Guessing a udev rule to overwrite the fuzz") 380a46c0ec8Sopenharmony_ci 381a46c0ec8Sopenharmony_ci # Some devices match better on pvr, others on pn, so we get to try both. yay 382a46c0ec8Sopenharmony_ci modalias = open("/sys/class/dmi/id/modalias").readlines()[0] 383a46c0ec8Sopenharmony_ci ms = modalias.split(":") 384a46c0ec8Sopenharmony_ci svn, pn, pvr = None, None, None 385a46c0ec8Sopenharmony_ci for m in ms: 386a46c0ec8Sopenharmony_ci if m.startswith("svn"): 387a46c0ec8Sopenharmony_ci svn = m 388a46c0ec8Sopenharmony_ci elif m.startswith("pn"): 389a46c0ec8Sopenharmony_ci pn = m 390a46c0ec8Sopenharmony_ci elif m.startswith("pvr"): 391a46c0ec8Sopenharmony_ci pvr = m 392a46c0ec8Sopenharmony_ci 393a46c0ec8Sopenharmony_ci # Let's print out both to inform and/or confuse the user 394a46c0ec8Sopenharmony_ci template = "\n".join( 395a46c0ec8Sopenharmony_ci ( 396a46c0ec8Sopenharmony_ci "# {} {}", 397a46c0ec8Sopenharmony_ci "evdev:name:{}:dmi:*:{}*:{}*:", 398a46c0ec8Sopenharmony_ci " EVDEV_ABS_00=:::{}", 399a46c0ec8Sopenharmony_ci " EVDEV_ABS_01=:::{}", 400a46c0ec8Sopenharmony_ci " EVDEV_ABS_35=:::{}", 401a46c0ec8Sopenharmony_ci " EVDEV_ABS_36=:::{}", 402a46c0ec8Sopenharmony_ci "", 403a46c0ec8Sopenharmony_ci ) 404a46c0ec8Sopenharmony_ci ) 405a46c0ec8Sopenharmony_ci rule1 = template.format( 406a46c0ec8Sopenharmony_ci svn[3:], device.name, device.name, svn, pvr, fuzz, fuzz, fuzz, fuzz 407a46c0ec8Sopenharmony_ci ) 408a46c0ec8Sopenharmony_ci rule2 = template.format( 409a46c0ec8Sopenharmony_ci svn[3:], device.name, device.name, svn, pn, fuzz, fuzz, fuzz, fuzz 410a46c0ec8Sopenharmony_ci ) 411a46c0ec8Sopenharmony_ci 412a46c0ec8Sopenharmony_ci print("Full modalias is: {}".format(modalias)) 413a46c0ec8Sopenharmony_ci print() 414a46c0ec8Sopenharmony_ci print_bold("Suggested udev rule, option 1:") 415a46c0ec8Sopenharmony_ci print(rule1) 416a46c0ec8Sopenharmony_ci print() 417a46c0ec8Sopenharmony_ci print_bold("Suggested udev rule, option 2:") 418a46c0ec8Sopenharmony_ci print(rule2) 419a46c0ec8Sopenharmony_ci print("") 420a46c0ec8Sopenharmony_ci 421a46c0ec8Sopenharmony_ci # The weird hwdb matching behavior means we match on the least specific 422a46c0ec8Sopenharmony_ci # rule (i.e. most wildcards) first although that was supposed to be fixed in 423a46c0ec8Sopenharmony_ci # systemd 3a04b789c6f1. 424a46c0ec8Sopenharmony_ci # Our rule uses dmi strings and will be more specific than what 60-evdev.hwdb 425a46c0ec8Sopenharmony_ci # already has. So we basically throw up our hands because we can't do anything 426a46c0ec8Sopenharmony_ci # then. 427a46c0ec8Sopenharmony_ci if handle_existing_entry(device, fuzz): 428a46c0ec8Sopenharmony_ci return 429a46c0ec8Sopenharmony_ci 430a46c0ec8Sopenharmony_ci while True: 431a46c0ec8Sopenharmony_ci print_bold("Wich rule do you want to to test? 1 or 2? ", end="") 432a46c0ec8Sopenharmony_ci yesno = input("Ctrl+C to exit ") 433a46c0ec8Sopenharmony_ci 434a46c0ec8Sopenharmony_ci if yesno == "1": 435a46c0ec8Sopenharmony_ci rule = rule1 436a46c0ec8Sopenharmony_ci break 437a46c0ec8Sopenharmony_ci elif yesno == "2": 438a46c0ec8Sopenharmony_ci rule = rule2 439a46c0ec8Sopenharmony_ci break 440a46c0ec8Sopenharmony_ci 441a46c0ec8Sopenharmony_ci fname = OVERRIDE_HWDB_FILE 442a46c0ec8Sopenharmony_ci try: 443a46c0ec8Sopenharmony_ci fd = open(fname, "x") 444a46c0ec8Sopenharmony_ci except FileExistsError: 445a46c0ec8Sopenharmony_ci yesno = input("File {} exists, overwrite? [Y/n] ".format(fname)) 446a46c0ec8Sopenharmony_ci if yesno.lower == "n": 447a46c0ec8Sopenharmony_ci return 448a46c0ec8Sopenharmony_ci 449a46c0ec8Sopenharmony_ci fd = open(fname, "w") 450a46c0ec8Sopenharmony_ci 451a46c0ec8Sopenharmony_ci fd.write("# File generated by libinput measure fuzz\n\n") 452a46c0ec8Sopenharmony_ci fd.write(rule) 453a46c0ec8Sopenharmony_ci fd.close() 454a46c0ec8Sopenharmony_ci 455a46c0ec8Sopenharmony_ci if test_hwdb_entry(device, fuzz): 456a46c0ec8Sopenharmony_ci print("Your hwdb override file is in {}".format(fname)) 457a46c0ec8Sopenharmony_ci print_bold("Please test the new fuzz setting by restarting libinput") 458a46c0ec8Sopenharmony_ci print_bold( 459a46c0ec8Sopenharmony_ci "Then submit a pull request for this hwdb entry to " 460a46c0ec8Sopenharmony_ci "systemd at http://github.com/systemd/systemd" 461a46c0ec8Sopenharmony_ci ) 462a46c0ec8Sopenharmony_ci else: 463a46c0ec8Sopenharmony_ci print("The hwdb entry failed to apply to the device.") 464a46c0ec8Sopenharmony_ci print("Removing hwdb file again.") 465a46c0ec8Sopenharmony_ci os.remove(fname) 466a46c0ec8Sopenharmony_ci reload_and_trigger_udev(device) 467a46c0ec8Sopenharmony_ci print_bold("What now?") 468a46c0ec8Sopenharmony_ci print( 469a46c0ec8Sopenharmony_ci "1. Re-run this program and try the other suggested udev rule. If that fails," 470a46c0ec8Sopenharmony_ci ) 471a46c0ec8Sopenharmony_ci print( 472a46c0ec8Sopenharmony_ci "2. File a bug with the suggested udev rule at http://github.com/systemd/systemd" 473a46c0ec8Sopenharmony_ci ) 474a46c0ec8Sopenharmony_ci 475a46c0ec8Sopenharmony_ci 476a46c0ec8Sopenharmony_cidef main(args): 477a46c0ec8Sopenharmony_ci parser = argparse.ArgumentParser( 478a46c0ec8Sopenharmony_ci description="Print fuzz settings and/or suggest udev rules for the fuzz to be adjusted." 479a46c0ec8Sopenharmony_ci ) 480a46c0ec8Sopenharmony_ci parser.add_argument( 481a46c0ec8Sopenharmony_ci "path", 482a46c0ec8Sopenharmony_ci metavar="/dev/input/event0", 483a46c0ec8Sopenharmony_ci nargs="?", 484a46c0ec8Sopenharmony_ci type=str, 485a46c0ec8Sopenharmony_ci help="Path to device (optional)", 486a46c0ec8Sopenharmony_ci ) 487a46c0ec8Sopenharmony_ci parser.add_argument("--fuzz", type=int, help="Suggested fuzz") 488a46c0ec8Sopenharmony_ci args = parser.parse_args() 489a46c0ec8Sopenharmony_ci 490a46c0ec8Sopenharmony_ci try: 491a46c0ec8Sopenharmony_ci device = Device(args.path) 492a46c0ec8Sopenharmony_ci print_bold("Using {}: {}".format(device.name, device.path)) 493a46c0ec8Sopenharmony_ci 494a46c0ec8Sopenharmony_ci fuzz = device.check_property() 495a46c0ec8Sopenharmony_ci print_fuzz("udev property", fuzz) 496a46c0ec8Sopenharmony_ci 497a46c0ec8Sopenharmony_ci fuzz = device.check_axes() 498a46c0ec8Sopenharmony_ci print_fuzz("axes", fuzz) 499a46c0ec8Sopenharmony_ci 500a46c0ec8Sopenharmony_ci userfuzz = args.fuzz 501a46c0ec8Sopenharmony_ci if userfuzz is not None: 502a46c0ec8Sopenharmony_ci write_udev_rule(device, userfuzz) 503a46c0ec8Sopenharmony_ci 504a46c0ec8Sopenharmony_ci except PermissionError: 505a46c0ec8Sopenharmony_ci print("Permission denied, please re-run as root") 506a46c0ec8Sopenharmony_ci except InvalidConfigurationError as e: 507a46c0ec8Sopenharmony_ci print("Error: {}".format(e)) 508a46c0ec8Sopenharmony_ci except InvalidDeviceError as e: 509a46c0ec8Sopenharmony_ci print("Error: {}".format(e)) 510a46c0ec8Sopenharmony_ci except KeyboardInterrupt: 511a46c0ec8Sopenharmony_ci print("Exited on user request") 512a46c0ec8Sopenharmony_ci 513a46c0ec8Sopenharmony_ci 514a46c0ec8Sopenharmony_ciif __name__ == "__main__": 515a46c0ec8Sopenharmony_ci main(sys.argv) 516