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