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 © 2020 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 argparse
29a46c0ec8Sopenharmony_ci
30a46c0ec8Sopenharmony_citry:
31a46c0ec8Sopenharmony_ci    import libevdev
32a46c0ec8Sopenharmony_ci    import pyudev
33a46c0ec8Sopenharmony_ciexcept ModuleNotFoundError as e:
34a46c0ec8Sopenharmony_ci    print("Error: {}".format(str(e)), file=sys.stderr)
35a46c0ec8Sopenharmony_ci    print(
36a46c0ec8Sopenharmony_ci        "One or more python modules are missing. Please install those "
37a46c0ec8Sopenharmony_ci        "modules and re-run this tool."
38a46c0ec8Sopenharmony_ci    )
39a46c0ec8Sopenharmony_ci    sys.exit(1)
40a46c0ec8Sopenharmony_ci
41a46c0ec8Sopenharmony_ci
42a46c0ec8Sopenharmony_ciclass DeviceError(Exception):
43a46c0ec8Sopenharmony_ci    pass
44a46c0ec8Sopenharmony_ci
45a46c0ec8Sopenharmony_ci
46a46c0ec8Sopenharmony_ciclass Point:
47a46c0ec8Sopenharmony_ci    def __init__(self, x=None, y=None):
48a46c0ec8Sopenharmony_ci        self.x = x
49a46c0ec8Sopenharmony_ci        self.y = y
50a46c0ec8Sopenharmony_ci
51a46c0ec8Sopenharmony_ci
52a46c0ec8Sopenharmony_ciclass Touchpad(object):
53a46c0ec8Sopenharmony_ci    def __init__(self, evdev):
54a46c0ec8Sopenharmony_ci        x = evdev.absinfo[libevdev.EV_ABS.ABS_X]
55a46c0ec8Sopenharmony_ci        y = evdev.absinfo[libevdev.EV_ABS.ABS_Y]
56a46c0ec8Sopenharmony_ci        if not x or not y:
57a46c0ec8Sopenharmony_ci            raise DeviceError("Device does not have an x or axis")
58a46c0ec8Sopenharmony_ci
59a46c0ec8Sopenharmony_ci        if not x.resolution or not y.resolution:
60a46c0ec8Sopenharmony_ci            print("Device does not have resolutions.", file=sys.stderr)
61a46c0ec8Sopenharmony_ci            x.resolution = 1
62a46c0ec8Sopenharmony_ci            y.resolution = 1
63a46c0ec8Sopenharmony_ci
64a46c0ec8Sopenharmony_ci        self.xrange = x.maximum - x.minimum
65a46c0ec8Sopenharmony_ci        self.yrange = y.maximum - y.minimum
66a46c0ec8Sopenharmony_ci        self.width = self.xrange / x.resolution
67a46c0ec8Sopenharmony_ci        self.height = self.yrange / y.resolution
68a46c0ec8Sopenharmony_ci
69a46c0ec8Sopenharmony_ci        self._x = x
70a46c0ec8Sopenharmony_ci        self._y = y
71a46c0ec8Sopenharmony_ci
72a46c0ec8Sopenharmony_ci        # We try to make the touchpad at least look proportional. The
73a46c0ec8Sopenharmony_ci        # terminal character space is (guesswork) ca 2.3 times as high as
74a46c0ec8Sopenharmony_ci        # wide.
75a46c0ec8Sopenharmony_ci        self.columns = 30
76a46c0ec8Sopenharmony_ci        self.rows = int(
77a46c0ec8Sopenharmony_ci            self.columns
78a46c0ec8Sopenharmony_ci            * (self.yrange // y.resolution)
79a46c0ec8Sopenharmony_ci            // (self.xrange // x.resolution)
80a46c0ec8Sopenharmony_ci            / 2.3
81a46c0ec8Sopenharmony_ci        )
82a46c0ec8Sopenharmony_ci        self.pos = Point(0, 0)
83a46c0ec8Sopenharmony_ci        self.min = Point()
84a46c0ec8Sopenharmony_ci        self.max = Point()
85a46c0ec8Sopenharmony_ci
86a46c0ec8Sopenharmony_ci    @property
87a46c0ec8Sopenharmony_ci    def x(self):
88a46c0ec8Sopenharmony_ci        return self._x
89a46c0ec8Sopenharmony_ci
90a46c0ec8Sopenharmony_ci    @property
91a46c0ec8Sopenharmony_ci    def y(self):
92a46c0ec8Sopenharmony_ci        return self._y
93a46c0ec8Sopenharmony_ci
94a46c0ec8Sopenharmony_ci    @x.setter
95a46c0ec8Sopenharmony_ci    def x(self, x):
96a46c0ec8Sopenharmony_ci        self._x.minimum = min(self.x.minimum, x)
97a46c0ec8Sopenharmony_ci        self._x.maximum = max(self.x.maximum, x)
98a46c0ec8Sopenharmony_ci        self.min.x = min(x, self.min.x or 0xFFFFFFFF)
99a46c0ec8Sopenharmony_ci        self.max.x = max(x, self.max.x or -0xFFFFFFFF)
100a46c0ec8Sopenharmony_ci        # we calculate the position based on the original range.
101a46c0ec8Sopenharmony_ci        # this means on devices with a narrower range than advertised, not
102a46c0ec8Sopenharmony_ci        # all corners may be reachable in the touchpad drawing.
103a46c0ec8Sopenharmony_ci        self.pos.x = min(0.99, (x - self._x.minimum) / self.xrange)
104a46c0ec8Sopenharmony_ci
105a46c0ec8Sopenharmony_ci    @y.setter
106a46c0ec8Sopenharmony_ci    def y(self, y):
107a46c0ec8Sopenharmony_ci        self._y.minimum = min(self.y.minimum, y)
108a46c0ec8Sopenharmony_ci        self._y.maximum = max(self.y.maximum, y)
109a46c0ec8Sopenharmony_ci        self.min.y = min(y, self.min.y or 0xFFFFFFFF)
110a46c0ec8Sopenharmony_ci        self.max.y = max(y, self.max.y or -0xFFFFFFFF)
111a46c0ec8Sopenharmony_ci        # we calculate the position based on the original range.
112a46c0ec8Sopenharmony_ci        # this means on devices with a narrower range than advertised, not
113a46c0ec8Sopenharmony_ci        # all corners may be reachable in the touchpad drawing.
114a46c0ec8Sopenharmony_ci        self.pos.y = min(0.99, (y - self._y.minimum) / self.yrange)
115a46c0ec8Sopenharmony_ci
116a46c0ec8Sopenharmony_ci    def update_from_data(self):
117a46c0ec8Sopenharmony_ci        if None in [self.min.x, self.min.y, self.max.x, self.max.y]:
118a46c0ec8Sopenharmony_ci            raise DeviceError("Insufficient data to continue")
119a46c0ec8Sopenharmony_ci        self._x.minimum = self.min.x
120a46c0ec8Sopenharmony_ci        self._x.maximum = self.max.x
121a46c0ec8Sopenharmony_ci        self._y.minimum = self.min.y
122a46c0ec8Sopenharmony_ci        self._y.maximum = self.max.y
123a46c0ec8Sopenharmony_ci
124a46c0ec8Sopenharmony_ci    def draw(self):
125a46c0ec8Sopenharmony_ci        print(
126a46c0ec8Sopenharmony_ci            "Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format(
127a46c0ec8Sopenharmony_ci                self.min.x if self.min.x is not None else 0,
128a46c0ec8Sopenharmony_ci                self.max.x if self.max.x is not None else 0,
129a46c0ec8Sopenharmony_ci                self.min.y if self.min.y is not None else 0,
130a46c0ec8Sopenharmony_ci                self.max.y if self.max.y is not None else 0,
131a46c0ec8Sopenharmony_ci            )
132a46c0ec8Sopenharmony_ci        )
133a46c0ec8Sopenharmony_ci
134a46c0ec8Sopenharmony_ci        print()
135a46c0ec8Sopenharmony_ci        print("Move one finger along all edges of the touchpad".center(self.columns))
136a46c0ec8Sopenharmony_ci        print("until the detected axis range stops changing.".center(self.columns))
137a46c0ec8Sopenharmony_ci
138a46c0ec8Sopenharmony_ci        top = int(self.pos.y * self.rows)
139a46c0ec8Sopenharmony_ci
140a46c0ec8Sopenharmony_ci        print("+{}+".format("".ljust(self.columns, "-")))
141a46c0ec8Sopenharmony_ci        for row in range(0, top):
142a46c0ec8Sopenharmony_ci            print("|{}|".format("".ljust(self.columns)))
143a46c0ec8Sopenharmony_ci
144a46c0ec8Sopenharmony_ci        left = int(self.pos.x * self.columns)
145a46c0ec8Sopenharmony_ci        right = max(0, self.columns - 1 - left)
146a46c0ec8Sopenharmony_ci        print("|{}{}{}|".format("".ljust(left), "O", "".ljust(right)))
147a46c0ec8Sopenharmony_ci
148a46c0ec8Sopenharmony_ci        for row in range(top + 1, self.rows):
149a46c0ec8Sopenharmony_ci            print("|{}|".format("".ljust(self.columns)))
150a46c0ec8Sopenharmony_ci
151a46c0ec8Sopenharmony_ci        print("+{}+".format("".ljust(self.columns, "-")))
152a46c0ec8Sopenharmony_ci
153a46c0ec8Sopenharmony_ci        print("Press Ctrl+C to stop".center(self.columns))
154a46c0ec8Sopenharmony_ci
155a46c0ec8Sopenharmony_ci        print("\033[{}A".format(self.rows + 8), flush=True)
156a46c0ec8Sopenharmony_ci
157a46c0ec8Sopenharmony_ci        self.rows_printed = self.rows + 8
158a46c0ec8Sopenharmony_ci
159a46c0ec8Sopenharmony_ci    def erase(self):
160a46c0ec8Sopenharmony_ci        # Erase all previous lines so we're not left with rubbish
161a46c0ec8Sopenharmony_ci        for row in range(self.rows_printed):
162a46c0ec8Sopenharmony_ci            print("\033[K")
163a46c0ec8Sopenharmony_ci        print("\033[{}A".format(self.rows_printed))
164a46c0ec8Sopenharmony_ci
165a46c0ec8Sopenharmony_ci
166a46c0ec8Sopenharmony_cidef dimension(string):
167a46c0ec8Sopenharmony_ci    try:
168a46c0ec8Sopenharmony_ci        ts = string.split("x")
169a46c0ec8Sopenharmony_ci        t = tuple([int(x) for x in ts])
170a46c0ec8Sopenharmony_ci        if len(t) == 2:
171a46c0ec8Sopenharmony_ci            return t
172a46c0ec8Sopenharmony_ci    except:  # noqa
173a46c0ec8Sopenharmony_ci        pass
174a46c0ec8Sopenharmony_ci
175a46c0ec8Sopenharmony_ci    msg = "{} is not in format WxH".format(string)
176a46c0ec8Sopenharmony_ci    raise argparse.ArgumentTypeError(msg)
177a46c0ec8Sopenharmony_ci
178a46c0ec8Sopenharmony_ci
179a46c0ec8Sopenharmony_cidef between(v1, v2, deviation):
180a46c0ec8Sopenharmony_ci    return v1 - deviation < v2 < v1 + deviation
181a46c0ec8Sopenharmony_ci
182a46c0ec8Sopenharmony_ci
183a46c0ec8Sopenharmony_cidef dmi_modalias_match(modalias):
184a46c0ec8Sopenharmony_ci    modalias = modalias.split(":")
185a46c0ec8Sopenharmony_ci    dmi = {"svn": None, "pvr": None, "pn": None}
186a46c0ec8Sopenharmony_ci    for m in modalias:
187a46c0ec8Sopenharmony_ci        for key in dmi:
188a46c0ec8Sopenharmony_ci            if m.startswith(key):
189a46c0ec8Sopenharmony_ci                dmi[key] = m[len(key) :]
190a46c0ec8Sopenharmony_ci
191a46c0ec8Sopenharmony_ci    # Based on the current 60-evdev.hwdb, Lenovo uses pvr and everyone else
192a46c0ec8Sopenharmony_ci    # uses pn to provide a human-identifiable match
193a46c0ec8Sopenharmony_ci    if dmi["svn"] == "LENOVO":
194a46c0ec8Sopenharmony_ci        return "dmi:*svn{}:*pvr{}*".format(dmi["svn"], dmi["pvr"])
195a46c0ec8Sopenharmony_ci    else:
196a46c0ec8Sopenharmony_ci        return "dmi:*svn{}:*pn{}*".format(dmi["svn"], dmi["pn"])
197a46c0ec8Sopenharmony_ci
198a46c0ec8Sopenharmony_ci
199a46c0ec8Sopenharmony_cidef main(args):
200a46c0ec8Sopenharmony_ci    parser = argparse.ArgumentParser(description="Measure the touchpad size")
201a46c0ec8Sopenharmony_ci    parser.add_argument(
202a46c0ec8Sopenharmony_ci        "size",
203a46c0ec8Sopenharmony_ci        metavar="WxH",
204a46c0ec8Sopenharmony_ci        type=dimension,
205a46c0ec8Sopenharmony_ci        help="Touchpad size (width by height) in mm",
206a46c0ec8Sopenharmony_ci    )
207a46c0ec8Sopenharmony_ci    parser.add_argument(
208a46c0ec8Sopenharmony_ci        "path",
209a46c0ec8Sopenharmony_ci        metavar="/dev/input/event0",
210a46c0ec8Sopenharmony_ci        nargs="?",
211a46c0ec8Sopenharmony_ci        type=str,
212a46c0ec8Sopenharmony_ci        help="Path to device (optional)",
213a46c0ec8Sopenharmony_ci    )
214a46c0ec8Sopenharmony_ci    context = pyudev.Context()
215a46c0ec8Sopenharmony_ci
216a46c0ec8Sopenharmony_ci    args = parser.parse_args()
217a46c0ec8Sopenharmony_ci    if not args.path:
218a46c0ec8Sopenharmony_ci        for device in context.list_devices(subsystem="input"):
219a46c0ec8Sopenharmony_ci            if device.get("ID_INPUT_TOUCHPAD", 0) and (
220a46c0ec8Sopenharmony_ci                device.device_node or ""
221a46c0ec8Sopenharmony_ci            ).startswith("/dev/input/event"):
222a46c0ec8Sopenharmony_ci                args.path = device.device_node
223a46c0ec8Sopenharmony_ci                name = "unknown"
224a46c0ec8Sopenharmony_ci                parent = device
225a46c0ec8Sopenharmony_ci                while parent is not None:
226a46c0ec8Sopenharmony_ci                    n = parent.get("NAME", None)
227a46c0ec8Sopenharmony_ci                    if n:
228a46c0ec8Sopenharmony_ci                        name = n
229a46c0ec8Sopenharmony_ci                        break
230a46c0ec8Sopenharmony_ci                    parent = parent.parent
231a46c0ec8Sopenharmony_ci
232a46c0ec8Sopenharmony_ci                print("Using {}: {}".format(name, device.device_node))
233a46c0ec8Sopenharmony_ci                break
234a46c0ec8Sopenharmony_ci        else:
235a46c0ec8Sopenharmony_ci            print("Unable to find a touchpad device.", file=sys.stderr)
236a46c0ec8Sopenharmony_ci            return 1
237a46c0ec8Sopenharmony_ci
238a46c0ec8Sopenharmony_ci    dev = pyudev.Devices.from_device_file(context, args.path)
239a46c0ec8Sopenharmony_ci    overrides = [p for p in dev.properties if p.startswith("EVDEV_ABS")]
240a46c0ec8Sopenharmony_ci    if overrides:
241a46c0ec8Sopenharmony_ci        print()
242a46c0ec8Sopenharmony_ci        print("********************************************************************")
243a46c0ec8Sopenharmony_ci        print("WARNING: axis overrides already in place for this device:")
244a46c0ec8Sopenharmony_ci        for prop in overrides:
245a46c0ec8Sopenharmony_ci            print("  {}={}".format(prop, dev.properties[prop]))
246a46c0ec8Sopenharmony_ci        print("The systemd hwdb already overrides the axis ranges and/or resolution.")
247a46c0ec8Sopenharmony_ci        print("This tool is not needed unless you want to verify the axis overrides.")
248a46c0ec8Sopenharmony_ci        print("********************************************************************")
249a46c0ec8Sopenharmony_ci        print()
250a46c0ec8Sopenharmony_ci
251a46c0ec8Sopenharmony_ci    try:
252a46c0ec8Sopenharmony_ci        fd = open(args.path, "rb")
253a46c0ec8Sopenharmony_ci        evdev = libevdev.Device(fd)
254a46c0ec8Sopenharmony_ci        touchpad = Touchpad(evdev)
255a46c0ec8Sopenharmony_ci        print(
256a46c0ec8Sopenharmony_ci            "Kernel specified touchpad size: {:.1f}x{:.1f}mm".format(
257a46c0ec8Sopenharmony_ci                touchpad.width, touchpad.height
258a46c0ec8Sopenharmony_ci            )
259a46c0ec8Sopenharmony_ci        )
260a46c0ec8Sopenharmony_ci        print("User specified touchpad size:   {:.1f}x{:.1f}mm".format(*args.size))
261a46c0ec8Sopenharmony_ci
262a46c0ec8Sopenharmony_ci        print()
263a46c0ec8Sopenharmony_ci        print(
264a46c0ec8Sopenharmony_ci            "Kernel axis range:   x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format(
265a46c0ec8Sopenharmony_ci                touchpad.x.minimum,
266a46c0ec8Sopenharmony_ci                touchpad.x.maximum,
267a46c0ec8Sopenharmony_ci                touchpad.y.minimum,
268a46c0ec8Sopenharmony_ci                touchpad.y.maximum,
269a46c0ec8Sopenharmony_ci            )
270a46c0ec8Sopenharmony_ci        )
271a46c0ec8Sopenharmony_ci
272a46c0ec8Sopenharmony_ci        print("Put your finger on the touchpad to start\033[1A")
273a46c0ec8Sopenharmony_ci
274a46c0ec8Sopenharmony_ci        try:
275a46c0ec8Sopenharmony_ci            touchpad.draw()
276a46c0ec8Sopenharmony_ci            while True:
277a46c0ec8Sopenharmony_ci                for event in evdev.events():
278a46c0ec8Sopenharmony_ci                    if event.matches(libevdev.EV_ABS.ABS_X):
279a46c0ec8Sopenharmony_ci                        touchpad.x = event.value
280a46c0ec8Sopenharmony_ci                    elif event.matches(libevdev.EV_ABS.ABS_Y):
281a46c0ec8Sopenharmony_ci                        touchpad.y = event.value
282a46c0ec8Sopenharmony_ci                    elif event.matches(libevdev.EV_SYN.SYN_REPORT):
283a46c0ec8Sopenharmony_ci                        touchpad.draw()
284a46c0ec8Sopenharmony_ci        except KeyboardInterrupt:
285a46c0ec8Sopenharmony_ci            touchpad.erase()
286a46c0ec8Sopenharmony_ci            touchpad.update_from_data()
287a46c0ec8Sopenharmony_ci
288a46c0ec8Sopenharmony_ci        print(
289a46c0ec8Sopenharmony_ci            "Detected axis range: x [{:4d}..{:4d}], y [{:4d}..{:4d}]".format(
290a46c0ec8Sopenharmony_ci                touchpad.x.minimum,
291a46c0ec8Sopenharmony_ci                touchpad.x.maximum,
292a46c0ec8Sopenharmony_ci                touchpad.y.minimum,
293a46c0ec8Sopenharmony_ci                touchpad.y.maximum,
294a46c0ec8Sopenharmony_ci            )
295a46c0ec8Sopenharmony_ci        )
296a46c0ec8Sopenharmony_ci
297a46c0ec8Sopenharmony_ci        touchpad.x.resolution = round(
298a46c0ec8Sopenharmony_ci            (touchpad.x.maximum - touchpad.x.minimum) / args.size[0]
299a46c0ec8Sopenharmony_ci        )
300a46c0ec8Sopenharmony_ci        touchpad.y.resolution = round(
301a46c0ec8Sopenharmony_ci            (touchpad.y.maximum - touchpad.y.minimum) / args.size[1]
302a46c0ec8Sopenharmony_ci        )
303a46c0ec8Sopenharmony_ci
304a46c0ec8Sopenharmony_ci        print(
305a46c0ec8Sopenharmony_ci            "Resolutions calculated based on user-specified size: x {}, y {} units/mm".format(
306a46c0ec8Sopenharmony_ci                touchpad.x.resolution, touchpad.y.resolution
307a46c0ec8Sopenharmony_ci            )
308a46c0ec8Sopenharmony_ci        )
309a46c0ec8Sopenharmony_ci
310a46c0ec8Sopenharmony_ci        # If both x/y are within some acceptable deviation, we skip the axis
311a46c0ec8Sopenharmony_ci        # overrides and only override the resolution
312a46c0ec8Sopenharmony_ci        xorig = evdev.absinfo[libevdev.EV_ABS.ABS_X]
313a46c0ec8Sopenharmony_ci        yorig = evdev.absinfo[libevdev.EV_ABS.ABS_Y]
314a46c0ec8Sopenharmony_ci        deviation = 1.5 * touchpad.x.resolution  # 1.5 mm rounding on each side
315a46c0ec8Sopenharmony_ci        skip = between(xorig.minimum, touchpad.x.minimum, deviation)
316a46c0ec8Sopenharmony_ci        skip = skip and between(xorig.maximum, touchpad.x.maximum, deviation)
317a46c0ec8Sopenharmony_ci        deviation = 1.5 * touchpad.y.resolution  # 1.5 mm rounding on each side
318a46c0ec8Sopenharmony_ci        skip = skip and between(yorig.minimum, touchpad.y.minimum, deviation)
319a46c0ec8Sopenharmony_ci        skip = skip and between(yorig.maximum, touchpad.y.maximum, deviation)
320a46c0ec8Sopenharmony_ci
321a46c0ec8Sopenharmony_ci        if skip:
322a46c0ec8Sopenharmony_ci            print()
323a46c0ec8Sopenharmony_ci            print(
324a46c0ec8Sopenharmony_ci                "Note: Axis ranges within acceptable deviation, skipping min/max override"
325a46c0ec8Sopenharmony_ci            )
326a46c0ec8Sopenharmony_ci            print()
327a46c0ec8Sopenharmony_ci
328a46c0ec8Sopenharmony_ci        print()
329a46c0ec8Sopenharmony_ci        print("Suggested hwdb entry:")
330a46c0ec8Sopenharmony_ci
331a46c0ec8Sopenharmony_ci        use_dmi = evdev.id["bustype"] not in [0x03, 0x05]  # USB, Bluetooth
332a46c0ec8Sopenharmony_ci        if use_dmi:
333a46c0ec8Sopenharmony_ci            modalias = open("/sys/class/dmi/id/modalias").read().strip()
334a46c0ec8Sopenharmony_ci            print(
335a46c0ec8Sopenharmony_ci                "Note: the dmi modalias match is a guess based on your machine's modalias:"
336a46c0ec8Sopenharmony_ci            )
337a46c0ec8Sopenharmony_ci            print(" ", modalias)
338a46c0ec8Sopenharmony_ci            print(
339a46c0ec8Sopenharmony_ci                "Please verify that this is the most sensible match and adjust if necessary."
340a46c0ec8Sopenharmony_ci            )
341a46c0ec8Sopenharmony_ci
342a46c0ec8Sopenharmony_ci        print("-8<--------------------------")
343a46c0ec8Sopenharmony_ci        print("# Laptop model description (e.g. Lenovo X1 Carbon 5th)")
344a46c0ec8Sopenharmony_ci        if use_dmi:
345a46c0ec8Sopenharmony_ci            print("evdev:name:{}:{}*".format(evdev.name, dmi_modalias_match(modalias)))
346a46c0ec8Sopenharmony_ci        else:
347a46c0ec8Sopenharmony_ci            print(
348a46c0ec8Sopenharmony_ci                "evdev:input:b{:04X}v{:04X}p{:04X}*".format(
349a46c0ec8Sopenharmony_ci                    evdev.id["bustype"], evdev.id["vendor"], evdev.id["product"]
350a46c0ec8Sopenharmony_ci                )
351a46c0ec8Sopenharmony_ci            )
352a46c0ec8Sopenharmony_ci        print(
353a46c0ec8Sopenharmony_ci            " EVDEV_ABS_00={}:{}:{}".format(
354a46c0ec8Sopenharmony_ci                touchpad.x.minimum if not skip else "",
355a46c0ec8Sopenharmony_ci                touchpad.x.maximum if not skip else "",
356a46c0ec8Sopenharmony_ci                touchpad.x.resolution,
357a46c0ec8Sopenharmony_ci            )
358a46c0ec8Sopenharmony_ci        )
359a46c0ec8Sopenharmony_ci        print(
360a46c0ec8Sopenharmony_ci            " EVDEV_ABS_01={}:{}:{}".format(
361a46c0ec8Sopenharmony_ci                touchpad.y.minimum if not skip else "",
362a46c0ec8Sopenharmony_ci                touchpad.y.maximum if not skip else "",
363a46c0ec8Sopenharmony_ci                touchpad.y.resolution,
364a46c0ec8Sopenharmony_ci            )
365a46c0ec8Sopenharmony_ci        )
366a46c0ec8Sopenharmony_ci        if evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X]:
367a46c0ec8Sopenharmony_ci            print(
368a46c0ec8Sopenharmony_ci                " EVDEV_ABS_35={}:{}:{}".format(
369a46c0ec8Sopenharmony_ci                    touchpad.x.minimum if not skip else "",
370a46c0ec8Sopenharmony_ci                    touchpad.x.maximum if not skip else "",
371a46c0ec8Sopenharmony_ci                    touchpad.x.resolution,
372a46c0ec8Sopenharmony_ci                )
373a46c0ec8Sopenharmony_ci            )
374a46c0ec8Sopenharmony_ci            print(
375a46c0ec8Sopenharmony_ci                " EVDEV_ABS_36={}:{}:{}".format(
376a46c0ec8Sopenharmony_ci                    touchpad.y.minimum if not skip else "",
377a46c0ec8Sopenharmony_ci                    touchpad.y.maximum if not skip else "",
378a46c0ec8Sopenharmony_ci                    touchpad.y.resolution,
379a46c0ec8Sopenharmony_ci                )
380a46c0ec8Sopenharmony_ci            )
381a46c0ec8Sopenharmony_ci        print("-8<--------------------------")
382a46c0ec8Sopenharmony_ci        print(
383a46c0ec8Sopenharmony_ci            "Instructions on what to do with this snippet are in /usr/lib/udev/hwdb.d/60-evdev.hwdb"
384a46c0ec8Sopenharmony_ci        )
385a46c0ec8Sopenharmony_ci    except DeviceError as e:
386a46c0ec8Sopenharmony_ci        print("Error: {}".format(e), file=sys.stderr)
387a46c0ec8Sopenharmony_ci        return 1
388a46c0ec8Sopenharmony_ci    except PermissionError:
389a46c0ec8Sopenharmony_ci        print("Unable to open device. Please run me as root", file=sys.stderr)
390a46c0ec8Sopenharmony_ci        return 1
391a46c0ec8Sopenharmony_ci
392a46c0ec8Sopenharmony_ci    return 0
393a46c0ec8Sopenharmony_ci
394a46c0ec8Sopenharmony_ci
395a46c0ec8Sopenharmony_ciif __name__ == "__main__":
396a46c0ec8Sopenharmony_ci    sys.exit(main(sys.argv))
397