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 Range(object):
44a46c0ec8Sopenharmony_ci    """Class to keep a min/max of a value around"""
45a46c0ec8Sopenharmony_ci
46a46c0ec8Sopenharmony_ci    def __init__(self):
47a46c0ec8Sopenharmony_ci        self.min = float("inf")
48a46c0ec8Sopenharmony_ci        self.max = float("-inf")
49a46c0ec8Sopenharmony_ci
50a46c0ec8Sopenharmony_ci    def update(self, value):
51a46c0ec8Sopenharmony_ci        self.min = min(self.min, value)
52a46c0ec8Sopenharmony_ci        self.max = max(self.max, value)
53a46c0ec8Sopenharmony_ci
54a46c0ec8Sopenharmony_ci
55a46c0ec8Sopenharmony_ciclass Touch(object):
56a46c0ec8Sopenharmony_ci    """A single data point of a sequence (i.e. one event frame)"""
57a46c0ec8Sopenharmony_ci
58a46c0ec8Sopenharmony_ci    def __init__(self, major=None, minor=None, orientation=None):
59a46c0ec8Sopenharmony_ci        self._major = major
60a46c0ec8Sopenharmony_ci        self._minor = minor
61a46c0ec8Sopenharmony_ci        self._orientation = orientation
62a46c0ec8Sopenharmony_ci        self.dirty = False
63a46c0ec8Sopenharmony_ci
64a46c0ec8Sopenharmony_ci    @property
65a46c0ec8Sopenharmony_ci    def major(self):
66a46c0ec8Sopenharmony_ci        return self._major
67a46c0ec8Sopenharmony_ci
68a46c0ec8Sopenharmony_ci    @major.setter
69a46c0ec8Sopenharmony_ci    def major(self, major):
70a46c0ec8Sopenharmony_ci        self._major = major
71a46c0ec8Sopenharmony_ci        self.dirty = True
72a46c0ec8Sopenharmony_ci
73a46c0ec8Sopenharmony_ci    @property
74a46c0ec8Sopenharmony_ci    def minor(self):
75a46c0ec8Sopenharmony_ci        return self._minor
76a46c0ec8Sopenharmony_ci
77a46c0ec8Sopenharmony_ci    @minor.setter
78a46c0ec8Sopenharmony_ci    def minor(self, minor):
79a46c0ec8Sopenharmony_ci        self._minor = minor
80a46c0ec8Sopenharmony_ci        self.dirty = True
81a46c0ec8Sopenharmony_ci
82a46c0ec8Sopenharmony_ci    @property
83a46c0ec8Sopenharmony_ci    def orientation(self):
84a46c0ec8Sopenharmony_ci        return self._orientation
85a46c0ec8Sopenharmony_ci
86a46c0ec8Sopenharmony_ci    @orientation.setter
87a46c0ec8Sopenharmony_ci    def orientation(self, orientation):
88a46c0ec8Sopenharmony_ci        self._orientation = orientation
89a46c0ec8Sopenharmony_ci        self.dirty = True
90a46c0ec8Sopenharmony_ci
91a46c0ec8Sopenharmony_ci    def __str__(self):
92a46c0ec8Sopenharmony_ci        s = "Touch: major {:3d}".format(self.major)
93a46c0ec8Sopenharmony_ci        if self.minor is not None:
94a46c0ec8Sopenharmony_ci            s += ", minor {:3d}".format(self.minor)
95a46c0ec8Sopenharmony_ci        if self.orientation is not None:
96a46c0ec8Sopenharmony_ci            s += ", orientation {:+3d}".format(self.orientation)
97a46c0ec8Sopenharmony_ci        return s
98a46c0ec8Sopenharmony_ci
99a46c0ec8Sopenharmony_ci
100a46c0ec8Sopenharmony_ciclass TouchSequence(object):
101a46c0ec8Sopenharmony_ci    """A touch sequence from beginning to end"""
102a46c0ec8Sopenharmony_ci
103a46c0ec8Sopenharmony_ci    def __init__(self, device, tracking_id):
104a46c0ec8Sopenharmony_ci        self.device = device
105a46c0ec8Sopenharmony_ci        self.tracking_id = tracking_id
106a46c0ec8Sopenharmony_ci        self.points = []
107a46c0ec8Sopenharmony_ci
108a46c0ec8Sopenharmony_ci        self.is_active = True
109a46c0ec8Sopenharmony_ci
110a46c0ec8Sopenharmony_ci        self.is_down = False
111a46c0ec8Sopenharmony_ci        self.was_down = False
112a46c0ec8Sopenharmony_ci        self.is_palm = False
113a46c0ec8Sopenharmony_ci        self.was_palm = False
114a46c0ec8Sopenharmony_ci        self.is_thumb = False
115a46c0ec8Sopenharmony_ci        self.was_thumb = False
116a46c0ec8Sopenharmony_ci
117a46c0ec8Sopenharmony_ci        self.major_range = Range()
118a46c0ec8Sopenharmony_ci        self.minor_range = Range()
119a46c0ec8Sopenharmony_ci
120a46c0ec8Sopenharmony_ci    def append(self, touch):
121a46c0ec8Sopenharmony_ci        """Add a Touch to the sequence"""
122a46c0ec8Sopenharmony_ci        self.points.append(touch)
123a46c0ec8Sopenharmony_ci        self.major_range.update(touch.major)
124a46c0ec8Sopenharmony_ci        self.minor_range.update(touch.minor)
125a46c0ec8Sopenharmony_ci
126a46c0ec8Sopenharmony_ci        if touch.major < self.device.up or touch.minor < self.device.up:
127a46c0ec8Sopenharmony_ci            self.is_down = False
128a46c0ec8Sopenharmony_ci        elif touch.major > self.device.down or touch.minor > self.device.down:
129a46c0ec8Sopenharmony_ci            self.is_down = True
130a46c0ec8Sopenharmony_ci            self.was_down = True
131a46c0ec8Sopenharmony_ci
132a46c0ec8Sopenharmony_ci        self.is_palm = touch.major > self.device.palm
133a46c0ec8Sopenharmony_ci        if self.is_palm:
134a46c0ec8Sopenharmony_ci            self.was_palm = True
135a46c0ec8Sopenharmony_ci
136a46c0ec8Sopenharmony_ci        self.is_thumb = self.device.thumb != 0 and touch.major > self.device.thumb
137a46c0ec8Sopenharmony_ci        if self.is_thumb:
138a46c0ec8Sopenharmony_ci            self.was_thumb = True
139a46c0ec8Sopenharmony_ci
140a46c0ec8Sopenharmony_ci    def finalize(self):
141a46c0ec8Sopenharmony_ci        """Mark the TouchSequence as complete (finger is up)"""
142a46c0ec8Sopenharmony_ci        self.is_active = False
143a46c0ec8Sopenharmony_ci
144a46c0ec8Sopenharmony_ci    def __str__(self):
145a46c0ec8Sopenharmony_ci        return self._str_state() if self.is_active else self._str_summary()
146a46c0ec8Sopenharmony_ci
147a46c0ec8Sopenharmony_ci    def _str_summary(self):
148a46c0ec8Sopenharmony_ci        if not self.points:
149a46c0ec8Sopenharmony_ci            return "{:78s}".format("Sequence: no major/minor values recorded")
150a46c0ec8Sopenharmony_ci
151a46c0ec8Sopenharmony_ci        s = "Sequence: major: [{:3d}..{:3d}] ".format(
152a46c0ec8Sopenharmony_ci            self.major_range.min, self.major_range.max
153a46c0ec8Sopenharmony_ci        )
154a46c0ec8Sopenharmony_ci        if self.device.has_minor:
155a46c0ec8Sopenharmony_ci            s += "minor: [{:3d}..{:3d}] ".format(
156a46c0ec8Sopenharmony_ci                self.minor_range.min, self.minor_range.max
157a46c0ec8Sopenharmony_ci            )
158a46c0ec8Sopenharmony_ci        if self.was_down:
159a46c0ec8Sopenharmony_ci            s += " down"
160a46c0ec8Sopenharmony_ci        if self.was_palm:
161a46c0ec8Sopenharmony_ci            s += " palm"
162a46c0ec8Sopenharmony_ci        if self.was_thumb:
163a46c0ec8Sopenharmony_ci            s += " thumb"
164a46c0ec8Sopenharmony_ci
165a46c0ec8Sopenharmony_ci        return s
166a46c0ec8Sopenharmony_ci
167a46c0ec8Sopenharmony_ci    def _str_state(self):
168a46c0ec8Sopenharmony_ci        touch = self.points[-1]
169a46c0ec8Sopenharmony_ci        s = "{}, tags: {} {} {}".format(
170a46c0ec8Sopenharmony_ci            touch,
171a46c0ec8Sopenharmony_ci            "down" if self.is_down else "    ",
172a46c0ec8Sopenharmony_ci            "palm" if self.is_palm else "    ",
173a46c0ec8Sopenharmony_ci            "thumb" if self.is_thumb else "     ",
174a46c0ec8Sopenharmony_ci        )
175a46c0ec8Sopenharmony_ci        return s
176a46c0ec8Sopenharmony_ci
177a46c0ec8Sopenharmony_ci
178a46c0ec8Sopenharmony_ciclass InvalidDeviceError(Exception):
179a46c0ec8Sopenharmony_ci    pass
180a46c0ec8Sopenharmony_ci
181a46c0ec8Sopenharmony_ci
182a46c0ec8Sopenharmony_ciclass Device(libevdev.Device):
183a46c0ec8Sopenharmony_ci    def __init__(self, path):
184a46c0ec8Sopenharmony_ci        if path is None:
185a46c0ec8Sopenharmony_ci            self.path = self.find_touch_device()
186a46c0ec8Sopenharmony_ci        else:
187a46c0ec8Sopenharmony_ci            self.path = path
188a46c0ec8Sopenharmony_ci
189a46c0ec8Sopenharmony_ci        fd = open(self.path, "rb")
190a46c0ec8Sopenharmony_ci        super().__init__(fd)
191a46c0ec8Sopenharmony_ci
192a46c0ec8Sopenharmony_ci        print("Using {}: {}\n".format(self.name, self.path))
193a46c0ec8Sopenharmony_ci
194a46c0ec8Sopenharmony_ci        if not self.has(libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR):
195a46c0ec8Sopenharmony_ci            raise InvalidDeviceError("Device does not have ABS_MT_TOUCH_MAJOR")
196a46c0ec8Sopenharmony_ci
197a46c0ec8Sopenharmony_ci        self.has_minor = self.has(libevdev.EV_ABS.ABS_MT_TOUCH_MINOR)
198a46c0ec8Sopenharmony_ci        self.has_orientation = self.has(libevdev.EV_ABS.ABS_MT_ORIENTATION)
199a46c0ec8Sopenharmony_ci
200a46c0ec8Sopenharmony_ci        self.up = 0
201a46c0ec8Sopenharmony_ci        self.down = 0
202a46c0ec8Sopenharmony_ci        self.palm = 0
203a46c0ec8Sopenharmony_ci        self.thumb = 0
204a46c0ec8Sopenharmony_ci
205a46c0ec8Sopenharmony_ci        self._init_thresholds_from_quirks()
206a46c0ec8Sopenharmony_ci        self.sequences = []
207a46c0ec8Sopenharmony_ci        self.touch = Touch(0, 0)
208a46c0ec8Sopenharmony_ci
209a46c0ec8Sopenharmony_ci    def find_touch_device(self):
210a46c0ec8Sopenharmony_ci        context = pyudev.Context()
211a46c0ec8Sopenharmony_ci        for device in context.list_devices(subsystem="input"):
212a46c0ec8Sopenharmony_ci            if not device.get("ID_INPUT_TOUCHPAD", 0) and not device.get(
213a46c0ec8Sopenharmony_ci                "ID_INPUT_TOUCHSCREEN", 0
214a46c0ec8Sopenharmony_ci            ):
215a46c0ec8Sopenharmony_ci                continue
216a46c0ec8Sopenharmony_ci
217a46c0ec8Sopenharmony_ci            if not device.device_node or not device.device_node.startswith(
218a46c0ec8Sopenharmony_ci                "/dev/input/event"
219a46c0ec8Sopenharmony_ci            ):
220a46c0ec8Sopenharmony_ci                continue
221a46c0ec8Sopenharmony_ci
222a46c0ec8Sopenharmony_ci            return device.device_node
223a46c0ec8Sopenharmony_ci
224a46c0ec8Sopenharmony_ci        print("Unable to find a touch device.", file=sys.stderr)
225a46c0ec8Sopenharmony_ci        sys.exit(1)
226a46c0ec8Sopenharmony_ci
227a46c0ec8Sopenharmony_ci    def _init_thresholds_from_quirks(self):
228a46c0ec8Sopenharmony_ci        command = ["libinput", "quirks", "list", self.path]
229a46c0ec8Sopenharmony_ci        cmd = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
230a46c0ec8Sopenharmony_ci        if cmd.returncode != 0:
231a46c0ec8Sopenharmony_ci            print(
232a46c0ec8Sopenharmony_ci                "Error querying quirks: {}".format(cmd.stderr.decode("utf-8")),
233a46c0ec8Sopenharmony_ci                file=sys.stderr,
234a46c0ec8Sopenharmony_ci            )
235a46c0ec8Sopenharmony_ci            return
236a46c0ec8Sopenharmony_ci
237a46c0ec8Sopenharmony_ci        stdout = cmd.stdout.decode("utf-8")
238a46c0ec8Sopenharmony_ci        quirks = [q.split("=") for q in stdout.split("\n")]
239a46c0ec8Sopenharmony_ci
240a46c0ec8Sopenharmony_ci        for q in quirks:
241a46c0ec8Sopenharmony_ci            if q[0] == "AttrPalmSizeThreshold":
242a46c0ec8Sopenharmony_ci                self.palm = int(q[1])
243a46c0ec8Sopenharmony_ci            elif q[0] == "AttrTouchSizeRange":
244a46c0ec8Sopenharmony_ci                self.down, self.up = colon_tuple(q[1])
245a46c0ec8Sopenharmony_ci            elif q[0] == "AttrThumbSizeThreshold":
246a46c0ec8Sopenharmony_ci                self.thumb = int(q[1])
247a46c0ec8Sopenharmony_ci
248a46c0ec8Sopenharmony_ci    def start_new_sequence(self, tracking_id):
249a46c0ec8Sopenharmony_ci        self.sequences.append(TouchSequence(self, tracking_id))
250a46c0ec8Sopenharmony_ci
251a46c0ec8Sopenharmony_ci    def current_sequence(self):
252a46c0ec8Sopenharmony_ci        return self.sequences[-1]
253a46c0ec8Sopenharmony_ci
254a46c0ec8Sopenharmony_ci    def handle_key(self, event):
255a46c0ec8Sopenharmony_ci        tapcodes = [
256a46c0ec8Sopenharmony_ci            libevdev.EV_KEY.BTN_TOOL_DOUBLETAP,
257a46c0ec8Sopenharmony_ci            libevdev.EV_KEY.BTN_TOOL_TRIPLETAP,
258a46c0ec8Sopenharmony_ci            libevdev.EV_KEY.BTN_TOOL_QUADTAP,
259a46c0ec8Sopenharmony_ci            libevdev.EV_KEY.BTN_TOOL_QUINTTAP,
260a46c0ec8Sopenharmony_ci        ]
261a46c0ec8Sopenharmony_ci        if event.code in tapcodes and event.value > 0:
262a46c0ec8Sopenharmony_ci            print(
263a46c0ec8Sopenharmony_ci                "\rThis tool cannot handle multiple fingers, " "output will be invalid",
264a46c0ec8Sopenharmony_ci                file=sys.stderr,
265a46c0ec8Sopenharmony_ci            )
266a46c0ec8Sopenharmony_ci
267a46c0ec8Sopenharmony_ci    def handle_abs(self, event):
268a46c0ec8Sopenharmony_ci        if event.matches(libevdev.EV_ABS.ABS_MT_TRACKING_ID):
269a46c0ec8Sopenharmony_ci            if event.value > -1:
270a46c0ec8Sopenharmony_ci                self.start_new_sequence(event.value)
271a46c0ec8Sopenharmony_ci            else:
272a46c0ec8Sopenharmony_ci                try:
273a46c0ec8Sopenharmony_ci                    s = self.current_sequence()
274a46c0ec8Sopenharmony_ci                    s.finalize()
275a46c0ec8Sopenharmony_ci                    print("\r{}".format(s))
276a46c0ec8Sopenharmony_ci                except IndexError:
277a46c0ec8Sopenharmony_ci                    # If the finger was down during start
278a46c0ec8Sopenharmony_ci                    pass
279a46c0ec8Sopenharmony_ci        elif event.matches(libevdev.EV_ABS.ABS_MT_TOUCH_MAJOR):
280a46c0ec8Sopenharmony_ci            self.touch.major = event.value
281a46c0ec8Sopenharmony_ci        elif event.matches(libevdev.EV_ABS.ABS_MT_TOUCH_MINOR):
282a46c0ec8Sopenharmony_ci            self.touch.minor = event.value
283a46c0ec8Sopenharmony_ci        elif event.matches(libevdev.EV_ABS.ABS_MT_ORIENTATION):
284a46c0ec8Sopenharmony_ci            self.touch.orientation = event.value
285a46c0ec8Sopenharmony_ci
286a46c0ec8Sopenharmony_ci    def handle_syn(self, event):
287a46c0ec8Sopenharmony_ci        if self.touch.dirty:
288a46c0ec8Sopenharmony_ci            try:
289a46c0ec8Sopenharmony_ci                self.current_sequence().append(self.touch)
290a46c0ec8Sopenharmony_ci                print("\r{}".format(self.current_sequence()), end="")
291a46c0ec8Sopenharmony_ci                self.touch = Touch(
292a46c0ec8Sopenharmony_ci                    major=self.touch.major,
293a46c0ec8Sopenharmony_ci                    minor=self.touch.minor,
294a46c0ec8Sopenharmony_ci                    orientation=self.touch.orientation,
295a46c0ec8Sopenharmony_ci                )
296a46c0ec8Sopenharmony_ci            except IndexError:
297a46c0ec8Sopenharmony_ci                pass
298a46c0ec8Sopenharmony_ci
299a46c0ec8Sopenharmony_ci    def handle_event(self, event):
300a46c0ec8Sopenharmony_ci        if event.matches(libevdev.EV_ABS):
301a46c0ec8Sopenharmony_ci            self.handle_abs(event)
302a46c0ec8Sopenharmony_ci        elif event.matches(libevdev.EV_KEY):
303a46c0ec8Sopenharmony_ci            self.handle_key(event)
304a46c0ec8Sopenharmony_ci        elif event.matches(libevdev.EV_SYN):
305a46c0ec8Sopenharmony_ci            self.handle_syn(event)
306a46c0ec8Sopenharmony_ci
307a46c0ec8Sopenharmony_ci    def read_events(self):
308a46c0ec8Sopenharmony_ci        print("Ready for recording data.")
309a46c0ec8Sopenharmony_ci        print("Touch sizes used: {}:{}".format(self.down, self.up))
310a46c0ec8Sopenharmony_ci        print("Palm size used: {}".format(self.palm))
311a46c0ec8Sopenharmony_ci        print("Thumb size used: {}".format(self.thumb))
312a46c0ec8Sopenharmony_ci        print(
313a46c0ec8Sopenharmony_ci            "Place a single finger on the device to measure touch size.\n"
314a46c0ec8Sopenharmony_ci            "Ctrl+C to exit\n"
315a46c0ec8Sopenharmony_ci        )
316a46c0ec8Sopenharmony_ci
317a46c0ec8Sopenharmony_ci        while True:
318a46c0ec8Sopenharmony_ci            for event in self.events():
319a46c0ec8Sopenharmony_ci                self.handle_event(event)
320a46c0ec8Sopenharmony_ci
321a46c0ec8Sopenharmony_ci
322a46c0ec8Sopenharmony_cidef colon_tuple(string):
323a46c0ec8Sopenharmony_ci    try:
324a46c0ec8Sopenharmony_ci        ts = string.split(":")
325a46c0ec8Sopenharmony_ci        t = tuple([int(x) for x in ts])
326a46c0ec8Sopenharmony_ci        if len(t) == 2 and t[0] >= t[1]:
327a46c0ec8Sopenharmony_ci            return t
328a46c0ec8Sopenharmony_ci    except:  # noqa
329a46c0ec8Sopenharmony_ci        pass
330a46c0ec8Sopenharmony_ci
331a46c0ec8Sopenharmony_ci    msg = "{} is not in format N:M (N >= M)".format(string)
332a46c0ec8Sopenharmony_ci    raise argparse.ArgumentTypeError(msg)
333a46c0ec8Sopenharmony_ci
334a46c0ec8Sopenharmony_ci
335a46c0ec8Sopenharmony_cidef main(args):
336a46c0ec8Sopenharmony_ci    parser = argparse.ArgumentParser(description="Measure touch size and orientation")
337a46c0ec8Sopenharmony_ci    parser.add_argument(
338a46c0ec8Sopenharmony_ci        "path",
339a46c0ec8Sopenharmony_ci        metavar="/dev/input/event0",
340a46c0ec8Sopenharmony_ci        nargs="?",
341a46c0ec8Sopenharmony_ci        type=str,
342a46c0ec8Sopenharmony_ci        help="Path to device (optional)",
343a46c0ec8Sopenharmony_ci    )
344a46c0ec8Sopenharmony_ci    parser.add_argument(
345a46c0ec8Sopenharmony_ci        "--touch-thresholds",
346a46c0ec8Sopenharmony_ci        metavar="down:up",
347a46c0ec8Sopenharmony_ci        type=colon_tuple,
348a46c0ec8Sopenharmony_ci        help="Thresholds when a touch is logically down or up",
349a46c0ec8Sopenharmony_ci    )
350a46c0ec8Sopenharmony_ci    parser.add_argument(
351a46c0ec8Sopenharmony_ci        "--palm-threshold",
352a46c0ec8Sopenharmony_ci        metavar="t",
353a46c0ec8Sopenharmony_ci        type=int,
354a46c0ec8Sopenharmony_ci        help="Threshold when a touch is a palm",
355a46c0ec8Sopenharmony_ci    )
356a46c0ec8Sopenharmony_ci    args = parser.parse_args()
357a46c0ec8Sopenharmony_ci
358a46c0ec8Sopenharmony_ci    try:
359a46c0ec8Sopenharmony_ci        device = Device(args.path)
360a46c0ec8Sopenharmony_ci
361a46c0ec8Sopenharmony_ci        if args.touch_thresholds is not None:
362a46c0ec8Sopenharmony_ci            device.down, device.up = args.touch_thresholds
363a46c0ec8Sopenharmony_ci
364a46c0ec8Sopenharmony_ci        if args.palm_threshold is not None:
365a46c0ec8Sopenharmony_ci            device.palm = args.palm_threshold
366a46c0ec8Sopenharmony_ci
367a46c0ec8Sopenharmony_ci        device.read_events()
368a46c0ec8Sopenharmony_ci    except KeyboardInterrupt:
369a46c0ec8Sopenharmony_ci        pass
370a46c0ec8Sopenharmony_ci    except (PermissionError, OSError):
371a46c0ec8Sopenharmony_ci        print("Error: failed to open device")
372a46c0ec8Sopenharmony_ci    except InvalidDeviceError as e:
373a46c0ec8Sopenharmony_ci        print(
374a46c0ec8Sopenharmony_ci            "This device does not have the capabilities for size-based touch detection."
375a46c0ec8Sopenharmony_ci        )
376a46c0ec8Sopenharmony_ci        print("Details: {}".format(e))
377a46c0ec8Sopenharmony_ci
378a46c0ec8Sopenharmony_ci
379a46c0ec8Sopenharmony_ciif __name__ == "__main__":
380a46c0ec8Sopenharmony_ci    main(sys.argv)
381