xref: /linux/tools/testing/selftests/hid/tests/base.py (revision ffb85d5c)
1*ffb85d5cSBenjamin Tissoires#!/bin/env python3
2*ffb85d5cSBenjamin Tissoires# SPDX-License-Identifier: GPL-2.0
3*ffb85d5cSBenjamin Tissoires# -*- coding: utf-8 -*-
4*ffb85d5cSBenjamin Tissoires#
5*ffb85d5cSBenjamin Tissoires# Copyright (c) 2017 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6*ffb85d5cSBenjamin Tissoires# Copyright (c) 2017 Red Hat, Inc.
7*ffb85d5cSBenjamin Tissoires
8*ffb85d5cSBenjamin Tissoiresimport libevdev
9*ffb85d5cSBenjamin Tissoiresimport os
10*ffb85d5cSBenjamin Tissoiresimport pytest
11*ffb85d5cSBenjamin Tissoiresimport time
12*ffb85d5cSBenjamin Tissoires
13*ffb85d5cSBenjamin Tissoiresimport logging
14*ffb85d5cSBenjamin Tissoires
15*ffb85d5cSBenjamin Tissoiresfrom hidtools.device.base_device import BaseDevice, EvdevMatch, SysfsFile
16*ffb85d5cSBenjamin Tissoiresfrom pathlib import Path
17*ffb85d5cSBenjamin Tissoiresfrom typing import Final
18*ffb85d5cSBenjamin Tissoires
19*ffb85d5cSBenjamin Tissoireslogger = logging.getLogger("hidtools.test.base")
20*ffb85d5cSBenjamin Tissoires
21*ffb85d5cSBenjamin Tissoires# application to matches
22*ffb85d5cSBenjamin Tissoiresapplication_matches: Final = {
23*ffb85d5cSBenjamin Tissoires    # pyright: ignore
24*ffb85d5cSBenjamin Tissoires    "Accelerometer": EvdevMatch(
25*ffb85d5cSBenjamin Tissoires        req_properties=[
26*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
27*ffb85d5cSBenjamin Tissoires        ]
28*ffb85d5cSBenjamin Tissoires    ),
29*ffb85d5cSBenjamin Tissoires    "Game Pad": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
30*ffb85d5cSBenjamin Tissoires        requires=[
31*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_X,
32*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_Y,
33*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_RX,
34*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_RY,
35*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_START,
36*ffb85d5cSBenjamin Tissoires        ],
37*ffb85d5cSBenjamin Tissoires        excl_properties=[
38*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
39*ffb85d5cSBenjamin Tissoires        ],
40*ffb85d5cSBenjamin Tissoires    ),
41*ffb85d5cSBenjamin Tissoires    "Joystick": EvdevMatch(  # in systemd, this is a lot more complex, but that will do
42*ffb85d5cSBenjamin Tissoires        requires=[
43*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_RX,
44*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_RY,
45*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_START,
46*ffb85d5cSBenjamin Tissoires        ],
47*ffb85d5cSBenjamin Tissoires        excl_properties=[
48*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
49*ffb85d5cSBenjamin Tissoires        ],
50*ffb85d5cSBenjamin Tissoires    ),
51*ffb85d5cSBenjamin Tissoires    "Key": EvdevMatch(
52*ffb85d5cSBenjamin Tissoires        requires=[
53*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.KEY_A,
54*ffb85d5cSBenjamin Tissoires        ],
55*ffb85d5cSBenjamin Tissoires        excl_properties=[
56*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
57*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_DIRECT,
58*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_POINTER,
59*ffb85d5cSBenjamin Tissoires        ],
60*ffb85d5cSBenjamin Tissoires    ),
61*ffb85d5cSBenjamin Tissoires    "Mouse": EvdevMatch(
62*ffb85d5cSBenjamin Tissoires        requires=[
63*ffb85d5cSBenjamin Tissoires            libevdev.EV_REL.REL_X,
64*ffb85d5cSBenjamin Tissoires            libevdev.EV_REL.REL_Y,
65*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_LEFT,
66*ffb85d5cSBenjamin Tissoires        ],
67*ffb85d5cSBenjamin Tissoires        excl_properties=[
68*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
69*ffb85d5cSBenjamin Tissoires        ],
70*ffb85d5cSBenjamin Tissoires    ),
71*ffb85d5cSBenjamin Tissoires    "Pad": EvdevMatch(
72*ffb85d5cSBenjamin Tissoires        requires=[
73*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_0,
74*ffb85d5cSBenjamin Tissoires        ],
75*ffb85d5cSBenjamin Tissoires        excludes=[
76*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_TOOL_PEN,
77*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_TOUCH,
78*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_DISTANCE,
79*ffb85d5cSBenjamin Tissoires        ],
80*ffb85d5cSBenjamin Tissoires        excl_properties=[
81*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
82*ffb85d5cSBenjamin Tissoires        ],
83*ffb85d5cSBenjamin Tissoires    ),
84*ffb85d5cSBenjamin Tissoires    "Pen": EvdevMatch(
85*ffb85d5cSBenjamin Tissoires        requires=[
86*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_STYLUS,
87*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_X,
88*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_Y,
89*ffb85d5cSBenjamin Tissoires        ],
90*ffb85d5cSBenjamin Tissoires        excl_properties=[
91*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
92*ffb85d5cSBenjamin Tissoires        ],
93*ffb85d5cSBenjamin Tissoires    ),
94*ffb85d5cSBenjamin Tissoires    "Stylus": EvdevMatch(
95*ffb85d5cSBenjamin Tissoires        requires=[
96*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_STYLUS,
97*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_X,
98*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_Y,
99*ffb85d5cSBenjamin Tissoires        ],
100*ffb85d5cSBenjamin Tissoires        excl_properties=[
101*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
102*ffb85d5cSBenjamin Tissoires        ],
103*ffb85d5cSBenjamin Tissoires    ),
104*ffb85d5cSBenjamin Tissoires    "Touch Pad": EvdevMatch(
105*ffb85d5cSBenjamin Tissoires        requires=[
106*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_LEFT,
107*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_X,
108*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_Y,
109*ffb85d5cSBenjamin Tissoires        ],
110*ffb85d5cSBenjamin Tissoires        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
111*ffb85d5cSBenjamin Tissoires        req_properties=[
112*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_POINTER,
113*ffb85d5cSBenjamin Tissoires        ],
114*ffb85d5cSBenjamin Tissoires        excl_properties=[
115*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
116*ffb85d5cSBenjamin Tissoires        ],
117*ffb85d5cSBenjamin Tissoires    ),
118*ffb85d5cSBenjamin Tissoires    "Touch Screen": EvdevMatch(
119*ffb85d5cSBenjamin Tissoires        requires=[
120*ffb85d5cSBenjamin Tissoires            libevdev.EV_KEY.BTN_TOUCH,
121*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_X,
122*ffb85d5cSBenjamin Tissoires            libevdev.EV_ABS.ABS_Y,
123*ffb85d5cSBenjamin Tissoires        ],
124*ffb85d5cSBenjamin Tissoires        excludes=[libevdev.EV_KEY.BTN_TOOL_PEN, libevdev.EV_KEY.BTN_STYLUS],
125*ffb85d5cSBenjamin Tissoires        req_properties=[
126*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_DIRECT,
127*ffb85d5cSBenjamin Tissoires        ],
128*ffb85d5cSBenjamin Tissoires        excl_properties=[
129*ffb85d5cSBenjamin Tissoires            libevdev.INPUT_PROP_ACCELEROMETER,
130*ffb85d5cSBenjamin Tissoires        ],
131*ffb85d5cSBenjamin Tissoires    ),
132*ffb85d5cSBenjamin Tissoires}
133*ffb85d5cSBenjamin Tissoires
134*ffb85d5cSBenjamin Tissoires
135*ffb85d5cSBenjamin Tissoiresclass UHIDTestDevice(BaseDevice):
136*ffb85d5cSBenjamin Tissoires    def __init__(self, name, application, rdesc_str=None, rdesc=None, input_info=None):
137*ffb85d5cSBenjamin Tissoires        super().__init__(name, application, rdesc_str, rdesc, input_info)
138*ffb85d5cSBenjamin Tissoires        self.application_matches = application_matches
139*ffb85d5cSBenjamin Tissoires        if name is None:
140*ffb85d5cSBenjamin Tissoires            name = f"uhid test {self.__class__.__name__}"
141*ffb85d5cSBenjamin Tissoires        if not name.startswith("uhid test "):
142*ffb85d5cSBenjamin Tissoires            name = "uhid test " + self.name
143*ffb85d5cSBenjamin Tissoires        self.name = name
144*ffb85d5cSBenjamin Tissoires
145*ffb85d5cSBenjamin Tissoires
146*ffb85d5cSBenjamin Tissoiresclass BaseTestCase:
147*ffb85d5cSBenjamin Tissoires    class TestUhid(object):
148*ffb85d5cSBenjamin Tissoires        syn_event = libevdev.InputEvent(libevdev.EV_SYN.SYN_REPORT)  # type: ignore
149*ffb85d5cSBenjamin Tissoires        key_event = libevdev.InputEvent(libevdev.EV_KEY)  # type: ignore
150*ffb85d5cSBenjamin Tissoires        abs_event = libevdev.InputEvent(libevdev.EV_ABS)  # type: ignore
151*ffb85d5cSBenjamin Tissoires        rel_event = libevdev.InputEvent(libevdev.EV_REL)  # type: ignore
152*ffb85d5cSBenjamin Tissoires        msc_event = libevdev.InputEvent(libevdev.EV_MSC.MSC_SCAN)  # type: ignore
153*ffb85d5cSBenjamin Tissoires
154*ffb85d5cSBenjamin Tissoires        # List of kernel modules to load before starting the test
155*ffb85d5cSBenjamin Tissoires        # if any module is not available (not compiled), the test will skip.
156*ffb85d5cSBenjamin Tissoires        # Each element is a tuple '(kernel driver name, kernel module)',
157*ffb85d5cSBenjamin Tissoires        # for example ("playstation", "hid-playstation")
158*ffb85d5cSBenjamin Tissoires        kernel_modules = []
159*ffb85d5cSBenjamin Tissoires
160*ffb85d5cSBenjamin Tissoires        def assertInputEventsIn(self, expected_events, effective_events):
161*ffb85d5cSBenjamin Tissoires            effective_events = effective_events.copy()
162*ffb85d5cSBenjamin Tissoires            for ev in expected_events:
163*ffb85d5cSBenjamin Tissoires                assert ev in effective_events
164*ffb85d5cSBenjamin Tissoires                effective_events.remove(ev)
165*ffb85d5cSBenjamin Tissoires            return effective_events
166*ffb85d5cSBenjamin Tissoires
167*ffb85d5cSBenjamin Tissoires        def assertInputEvents(self, expected_events, effective_events):
168*ffb85d5cSBenjamin Tissoires            remaining = self.assertInputEventsIn(expected_events, effective_events)
169*ffb85d5cSBenjamin Tissoires            assert remaining == []
170*ffb85d5cSBenjamin Tissoires
171*ffb85d5cSBenjamin Tissoires        @classmethod
172*ffb85d5cSBenjamin Tissoires        def debug_reports(cls, reports, uhdev=None, events=None):
173*ffb85d5cSBenjamin Tissoires            data = [" ".join([f"{v:02x}" for v in r]) for r in reports]
174*ffb85d5cSBenjamin Tissoires
175*ffb85d5cSBenjamin Tissoires            if uhdev is not None:
176*ffb85d5cSBenjamin Tissoires                human_data = [
177*ffb85d5cSBenjamin Tissoires                    uhdev.parsed_rdesc.format_report(r, split_lines=True)
178*ffb85d5cSBenjamin Tissoires                    for r in reports
179*ffb85d5cSBenjamin Tissoires                ]
180*ffb85d5cSBenjamin Tissoires                try:
181*ffb85d5cSBenjamin Tissoires                    human_data = [
182*ffb85d5cSBenjamin Tissoires                        f'\n\t       {" " * h.index("/")}'.join(h.split("\n"))
183*ffb85d5cSBenjamin Tissoires                        for h in human_data
184*ffb85d5cSBenjamin Tissoires                    ]
185*ffb85d5cSBenjamin Tissoires                except ValueError:
186*ffb85d5cSBenjamin Tissoires                    # '/' not found: not a numbered report
187*ffb85d5cSBenjamin Tissoires                    human_data = ["\n\t      ".join(h.split("\n")) for h in human_data]
188*ffb85d5cSBenjamin Tissoires                data = [f"{d}\n\t ====> {h}" for d, h in zip(data, human_data)]
189*ffb85d5cSBenjamin Tissoires
190*ffb85d5cSBenjamin Tissoires            reports = data
191*ffb85d5cSBenjamin Tissoires
192*ffb85d5cSBenjamin Tissoires            if len(reports) == 1:
193*ffb85d5cSBenjamin Tissoires                print("sending 1 report:")
194*ffb85d5cSBenjamin Tissoires            else:
195*ffb85d5cSBenjamin Tissoires                print(f"sending {len(reports)} reports:")
196*ffb85d5cSBenjamin Tissoires            for report in reports:
197*ffb85d5cSBenjamin Tissoires                print("\t", report)
198*ffb85d5cSBenjamin Tissoires
199*ffb85d5cSBenjamin Tissoires            if events is not None:
200*ffb85d5cSBenjamin Tissoires                print("events received:", events)
201*ffb85d5cSBenjamin Tissoires
202*ffb85d5cSBenjamin Tissoires        def create_device(self):
203*ffb85d5cSBenjamin Tissoires            raise Exception("please reimplement me in subclasses")
204*ffb85d5cSBenjamin Tissoires
205*ffb85d5cSBenjamin Tissoires        def _load_kernel_module(self, kernel_driver, kernel_module):
206*ffb85d5cSBenjamin Tissoires            sysfs_path = Path("/sys/bus/hid/drivers")
207*ffb85d5cSBenjamin Tissoires            if kernel_driver is not None:
208*ffb85d5cSBenjamin Tissoires                sysfs_path /= kernel_driver
209*ffb85d5cSBenjamin Tissoires            else:
210*ffb85d5cSBenjamin Tissoires                # special case for when testing all available modules:
211*ffb85d5cSBenjamin Tissoires                # we don't know beforehand the name of the module from modinfo
212*ffb85d5cSBenjamin Tissoires                sysfs_path = Path("/sys/module") / kernel_module.replace("-", "_")
213*ffb85d5cSBenjamin Tissoires            if not sysfs_path.exists():
214*ffb85d5cSBenjamin Tissoires                import subprocess
215*ffb85d5cSBenjamin Tissoires
216*ffb85d5cSBenjamin Tissoires                ret = subprocess.run(["/usr/sbin/modprobe", kernel_module])
217*ffb85d5cSBenjamin Tissoires                if ret.returncode != 0:
218*ffb85d5cSBenjamin Tissoires                    pytest.skip(
219*ffb85d5cSBenjamin Tissoires                        f"module {kernel_module} could not be loaded, skipping the test"
220*ffb85d5cSBenjamin Tissoires                    )
221*ffb85d5cSBenjamin Tissoires
222*ffb85d5cSBenjamin Tissoires        @pytest.fixture()
223*ffb85d5cSBenjamin Tissoires        def load_kernel_module(self):
224*ffb85d5cSBenjamin Tissoires            for kernel_driver, kernel_module in self.kernel_modules:
225*ffb85d5cSBenjamin Tissoires                self._load_kernel_module(kernel_driver, kernel_module)
226*ffb85d5cSBenjamin Tissoires            yield
227*ffb85d5cSBenjamin Tissoires
228*ffb85d5cSBenjamin Tissoires        @pytest.fixture()
229*ffb85d5cSBenjamin Tissoires        def new_uhdev(self, load_kernel_module):
230*ffb85d5cSBenjamin Tissoires            return self.create_device()
231*ffb85d5cSBenjamin Tissoires
232*ffb85d5cSBenjamin Tissoires        def assertName(self, uhdev):
233*ffb85d5cSBenjamin Tissoires            evdev = uhdev.get_evdev()
234*ffb85d5cSBenjamin Tissoires            assert uhdev.name in evdev.name
235*ffb85d5cSBenjamin Tissoires
236*ffb85d5cSBenjamin Tissoires        @pytest.fixture(autouse=True)
237*ffb85d5cSBenjamin Tissoires        def context(self, new_uhdev, request):
238*ffb85d5cSBenjamin Tissoires            try:
239*ffb85d5cSBenjamin Tissoires                with HIDTestUdevRule.instance():
240*ffb85d5cSBenjamin Tissoires                    with new_uhdev as self.uhdev:
241*ffb85d5cSBenjamin Tissoires                        skip_cond = request.node.get_closest_marker("skip_if_uhdev")
242*ffb85d5cSBenjamin Tissoires                        if skip_cond:
243*ffb85d5cSBenjamin Tissoires                            test, message, *rest = skip_cond.args
244*ffb85d5cSBenjamin Tissoires
245*ffb85d5cSBenjamin Tissoires                            if test(self.uhdev):
246*ffb85d5cSBenjamin Tissoires                                pytest.skip(message)
247*ffb85d5cSBenjamin Tissoires
248*ffb85d5cSBenjamin Tissoires                        self.uhdev.create_kernel_device()
249*ffb85d5cSBenjamin Tissoires                        now = time.time()
250*ffb85d5cSBenjamin Tissoires                        while not self.uhdev.is_ready() and time.time() - now < 5:
251*ffb85d5cSBenjamin Tissoires                            self.uhdev.dispatch(1)
252*ffb85d5cSBenjamin Tissoires                        if self.uhdev.get_evdev() is None:
253*ffb85d5cSBenjamin Tissoires                            logger.warning(
254*ffb85d5cSBenjamin Tissoires                                f"available list of input nodes: (default application is '{self.uhdev.application}')"
255*ffb85d5cSBenjamin Tissoires                            )
256*ffb85d5cSBenjamin Tissoires                            logger.warning(self.uhdev.input_nodes)
257*ffb85d5cSBenjamin Tissoires                        yield
258*ffb85d5cSBenjamin Tissoires                        self.uhdev = None
259*ffb85d5cSBenjamin Tissoires            except PermissionError:
260*ffb85d5cSBenjamin Tissoires                pytest.skip("Insufficient permissions, run me as root")
261*ffb85d5cSBenjamin Tissoires
262*ffb85d5cSBenjamin Tissoires        @pytest.fixture(autouse=True)
263*ffb85d5cSBenjamin Tissoires        def check_taint(self):
264*ffb85d5cSBenjamin Tissoires            # we are abusing SysfsFile here, it's in /proc, but meh
265*ffb85d5cSBenjamin Tissoires            taint_file = SysfsFile("/proc/sys/kernel/tainted")
266*ffb85d5cSBenjamin Tissoires            taint = taint_file.int_value
267*ffb85d5cSBenjamin Tissoires
268*ffb85d5cSBenjamin Tissoires            yield
269*ffb85d5cSBenjamin Tissoires
270*ffb85d5cSBenjamin Tissoires            assert taint_file.int_value == taint
271*ffb85d5cSBenjamin Tissoires
272*ffb85d5cSBenjamin Tissoires        def test_creation(self):
273*ffb85d5cSBenjamin Tissoires            """Make sure the device gets processed by the kernel and creates
274*ffb85d5cSBenjamin Tissoires            the expected application input node.
275*ffb85d5cSBenjamin Tissoires
276*ffb85d5cSBenjamin Tissoires            If this fail, there is something wrong in the device report
277*ffb85d5cSBenjamin Tissoires            descriptors."""
278*ffb85d5cSBenjamin Tissoires            uhdev = self.uhdev
279*ffb85d5cSBenjamin Tissoires            assert uhdev is not None
280*ffb85d5cSBenjamin Tissoires            assert uhdev.get_evdev() is not None
281*ffb85d5cSBenjamin Tissoires            self.assertName(uhdev)
282*ffb85d5cSBenjamin Tissoires            assert len(uhdev.next_sync_events()) == 0
283*ffb85d5cSBenjamin Tissoires            assert uhdev.get_evdev() is not None
284*ffb85d5cSBenjamin Tissoires
285*ffb85d5cSBenjamin Tissoires
286*ffb85d5cSBenjamin Tissoiresclass HIDTestUdevRule(object):
287*ffb85d5cSBenjamin Tissoires    _instance = None
288*ffb85d5cSBenjamin Tissoires    """
289*ffb85d5cSBenjamin Tissoires    A context-manager compatible class that sets up our udev rules file and
290*ffb85d5cSBenjamin Tissoires    deletes it on context exit.
291*ffb85d5cSBenjamin Tissoires
292*ffb85d5cSBenjamin Tissoires    This class is tailored to our test setup: it only sets up the udev rule
293*ffb85d5cSBenjamin Tissoires    on the **second** context and it cleans it up again on the last context
294*ffb85d5cSBenjamin Tissoires    removed. This matches the expected pytest setup: we enter a context for
295*ffb85d5cSBenjamin Tissoires    the session once, then once for each test (the first of which will
296*ffb85d5cSBenjamin Tissoires    trigger the udev rule) and once the last test exited and the session
297*ffb85d5cSBenjamin Tissoires    exited, we clean up after ourselves.
298*ffb85d5cSBenjamin Tissoires    """
299*ffb85d5cSBenjamin Tissoires
300*ffb85d5cSBenjamin Tissoires    def __init__(self):
301*ffb85d5cSBenjamin Tissoires        self.refs = 0
302*ffb85d5cSBenjamin Tissoires        self.rulesfile = None
303*ffb85d5cSBenjamin Tissoires
304*ffb85d5cSBenjamin Tissoires    def __enter__(self):
305*ffb85d5cSBenjamin Tissoires        self.refs += 1
306*ffb85d5cSBenjamin Tissoires        if self.refs == 2 and self.rulesfile is None:
307*ffb85d5cSBenjamin Tissoires            self.create_udev_rule()
308*ffb85d5cSBenjamin Tissoires            self.reload_udev_rules()
309*ffb85d5cSBenjamin Tissoires
310*ffb85d5cSBenjamin Tissoires    def __exit__(self, exc_type, exc_value, traceback):
311*ffb85d5cSBenjamin Tissoires        self.refs -= 1
312*ffb85d5cSBenjamin Tissoires        if self.refs == 0 and self.rulesfile:
313*ffb85d5cSBenjamin Tissoires            os.remove(self.rulesfile.name)
314*ffb85d5cSBenjamin Tissoires            self.reload_udev_rules()
315*ffb85d5cSBenjamin Tissoires
316*ffb85d5cSBenjamin Tissoires    def reload_udev_rules(self):
317*ffb85d5cSBenjamin Tissoires        import subprocess
318*ffb85d5cSBenjamin Tissoires
319*ffb85d5cSBenjamin Tissoires        subprocess.run("udevadm control --reload-rules".split())
320*ffb85d5cSBenjamin Tissoires        subprocess.run("systemd-hwdb update".split())
321*ffb85d5cSBenjamin Tissoires
322*ffb85d5cSBenjamin Tissoires    def create_udev_rule(self):
323*ffb85d5cSBenjamin Tissoires        import tempfile
324*ffb85d5cSBenjamin Tissoires
325*ffb85d5cSBenjamin Tissoires        os.makedirs("/run/udev/rules.d", exist_ok=True)
326*ffb85d5cSBenjamin Tissoires        with tempfile.NamedTemporaryFile(
327*ffb85d5cSBenjamin Tissoires            prefix="91-uhid-test-device-REMOVEME-",
328*ffb85d5cSBenjamin Tissoires            suffix=".rules",
329*ffb85d5cSBenjamin Tissoires            mode="w+",
330*ffb85d5cSBenjamin Tissoires            dir="/run/udev/rules.d",
331*ffb85d5cSBenjamin Tissoires            delete=False,
332*ffb85d5cSBenjamin Tissoires        ) as f:
333*ffb85d5cSBenjamin Tissoires            f.write(
334*ffb85d5cSBenjamin Tissoires                'KERNELS=="*input*", ATTRS{name}=="*uhid test *", ENV{LIBINPUT_IGNORE_DEVICE}="1"\n'
335*ffb85d5cSBenjamin Tissoires            )
336*ffb85d5cSBenjamin Tissoires            f.write(
337*ffb85d5cSBenjamin Tissoires                'KERNELS=="*input*", ATTRS{name}=="*uhid test * System Multi Axis", ENV{ID_INPUT_TOUCHSCREEN}="", ENV{ID_INPUT_SYSTEM_MULTIAXIS}="1"\n'
338*ffb85d5cSBenjamin Tissoires            )
339*ffb85d5cSBenjamin Tissoires            self.rulesfile = f
340*ffb85d5cSBenjamin Tissoires
341*ffb85d5cSBenjamin Tissoires    @classmethod
342*ffb85d5cSBenjamin Tissoires    def instance(cls):
343*ffb85d5cSBenjamin Tissoires        if not cls._instance:
344*ffb85d5cSBenjamin Tissoires            cls._instance = HIDTestUdevRule()
345*ffb85d5cSBenjamin Tissoires        return cls._instance
346