def __init__(
self,
name,
rdesc_str=None,
rdesc=None,
application="Touch Screen",
physical="Finger",
max_contacts=None,
input_info=(BusType.USB, 1, 2),
quirks=None,
):
super().__init__(name, application, rdesc_str, rdesc, input_info)
self.scantime = 0
self.quirks = quirks if max_contacts isNone:
self.max_contacts = sys.maxsize for features in self.parsed_rdesc.feature_reports.values(): for feature in features: if feature.usage_name in ["Contact Max"]:
self.max_contacts = feature.logical_max for inputs in self.parsed_rdesc.input_reports.values(): for i in inputs: if (
i.usage_name in ["Contact Count"] and i.logical_max > 0 and self.max_contacts > i.logical_max
):
self.max_contacts = i.logical_max if self.max_contacts == sys.maxsize:
self.max_contacts = 1 else:
self.max_contacts = max_contacts
self.physical = physical
self.cur_application = application
for features in self.parsed_rdesc.feature_reports.values(): for feature in features: if feature.usage_name == "Inputmode":
self.cur_application = "Mouse"
self.fields = [] for r in self.parsed_rdesc.input_reports.values(): if r.application_name == self.application:
physicals = [f.physical_name for f in r] if self.physical notin physicals andNonenotin physicals: continue
self.fields = [f.usage_name for f in r]
def event(self, slots, global_data=None, contact_count=None, incr_scantime=True): if incr_scantime:
self.scantime += 1
rs = [] # make sure we have only the required number of available slots
slots = slots[: self.max_contacts]
if global_data isNone:
global_data = Data() if contact_count isNone:
global_data.contactcount = len(slots) else:
global_data.contactcount = contact_count
global_data.scantime = self.scantime
while len(slots):
r = self.create_report(
application=self.cur_application, data=slots, global_data=global_data
)
self.call_input_event(r)
rs.append(r)
global_data.contactcount = 0 return rs
rdesc = None for v in self.parsed_rdesc.feature_reports.values(): if v.report_ID == rnum:
rdesc = v
if rdesc isNone: return 1
if"Inputmode"notin [f.usage_name for f in rdesc]: return 0
Inputmode_seen = False for f in rdesc: if"Inputmode" == f.usage_name:
values = f.get_values(data) assert len(values) == 1
value = values[0]
ifnot Inputmode_seen:
Inputmode_seen = True if value == 0:
self.cur_application = "Mouse" elif value == 2:
self.cur_application = "Touch Screen" elif value == 3:
self.cur_application = "Touch Pad" else: if value != 0: # Elan bug where the device doesn't work properly # if we set twice an Input Mode in the same Feature
self.cur_application = "Mouse"
def event(
self,
slots=None,
click=None,
left=None,
right=None,
contact_count=None,
incr_scantime=True,
): # update our internal state if click isnotNone:
self.clickpad_state = click if left isnotNone:
self.left_state = left if right isnotNone:
self.right_state = right
# now create the global data
global_data = Data()
global_data.b1 = 1 if self.clickpad_state else 0
global_data.b2 = 1 if self.left_state else 0
global_data.b3 = 1 if self.right_state else 0
def create_report(self, data, global_data=None, reportID=None, application=None): # this device has *a lot* of different reports, and most of them # have the Touch Screen application. But the first one is a stylus # report (as stated in the physical type), so we simply override # the report ID to use what the device sends return super().create_report(data, global_data=global_data, reportID=3)
def match_evdev_rule(self, application, evdev): # we need to select the correct evdev node, as the device has multiple # Touch Screen application collections if application != "Touch Screen": returnTrue
absinfo = evdev.absinfo[libevdev.EV_ABS.ABS_MT_POSITION_X] return absinfo isnotNoneand absinfo.resolution == 3
class BaseTest: class TestMultitouch(base.BaseTestCase.TestUhid):
kernel_modules = [KERNEL_MODULE]
def create_device(self): raise Exception("please reimplement me in subclasses")
def get_slot(self, uhdev, t, default): if uhdev.quirks isNone: return default
def test_creation(self): """Make sure the device gets processed by the kernel and creates
the expected application input node.
If this fail, there is something wrong in the device report
descriptors."""
super().test_creation()
uhdev = self.uhdev
evdev = uhdev.get_evdev()
# some sanity checking for the quirks if uhdev.quirks isnotNone: for q in uhdev.quirks: assert q in mt_quirks
assert evdev.num_slots == uhdev.max_contacts
if uhdev.max_contacts > 1: assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 if uhdev.max_contacts > 2: assert evdev.slots[2][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
def test_required_usages(self): """Make sure the device exports the correct required features and
inputs."""
uhdev = self.uhdev
rdesc = uhdev.parsed_rdesc for feature in rdesc.feature_reports.values(): for field in feature:
page_id = field.usage >> 16
value = field.usage & 0xFF try: if HUT[page_id][value] == "Contact Max": assert HUT[page_id][field.application] in [ "Touch Screen", "Touch Pad", "System Multi-Axis Controller",
] except KeyError: pass
try: if HUT[page_id][value] == "Inputmode": assert HUT[page_id][field.application] in [ "Touch Screen", "Touch Pad", "Device Configuration",
] except KeyError: pass
def test_mt_single_touch(self): """send a single touch in the first slot of the device, and release it."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
@pytest.mark.skip_if_uhdev( lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
) def test_mt_triple_tap(self): """Send 3 touches in the first 3 slots.
Make sure the kernel sees this as a triple touch.
Release and check
Note: PTP will send here BTN_TRIPLETAP emulation"""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
@pytest.mark.skip_if_uhdev( lambda uhdev: uhdev.max_contacts <= 2, "Device not compatible"
) def test_mt_max_contact(self): """send the maximum number of contact as reported by the device.
Make sure all contacts are forwarded and that there is no miss.
Release and check."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
touches = [
Touch(i, (i + 3) * 20, (i + 3) * 20 + 5) for i in range(uhdev.max_contacts)
] if (
uhdev.quirks isnotNone and"SLOT_IS_CONTACTID_MINUS_ONE"in uhdev.quirks
): for t in touches:
t.contactid += 1
r = uhdev.event(touches)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) for i, t in enumerate(touches):
slot = self.get_slot(uhdev, t, i)
for t in touches:
t.tipswitch = False if uhdev.quirks isNoneor"VALID_IS_INRANGE"notin uhdev.quirks:
t.inrange = False
r = uhdev.event(touches)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) for i, t in enumerate(touches):
slot = self.get_slot(uhdev, t, i)
@pytest.mark.skip_if_uhdev( lambda uhdev: (
uhdev.touches_in_a_report == 1 or uhdev.quirks isnotNone and"CONTACT_CNT_ACCURATE"notin uhdev.quirks
), "Device not compatible, we can not trigger the conditions",
) def test_mt_contact_count_accurate(self): """Test the MT_QUIRK_CONTACT_CNT_ACCURATE from the kernel.
A report should forward an accurate contact count and the kernel
should ignore any data provided after we have reached this
contact count."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
class TestWin8Multitouch(TestMultitouch): def test_required_usages8(self): """Make sure the device exports the correct required features and
inputs."""
uhdev = self.uhdev
rdesc = uhdev.parsed_rdesc for feature in rdesc.feature_reports.values(): for field in feature:
page_id = field.usage >> 16
value = field.usage & 0xFF try: if HUT[page_id][value] == "Inputmode": assert HUT[field.application] notin ["Touch Screen"] except KeyError: pass
@pytest.mark.skip_if_uhdev( lambda uhdev: uhdev.fields.count("X") == uhdev.touches_in_a_report, "Device not compatible, we can not trigger the conditions",
) def test_mt_tx_cx(self): """send a single touch in the first slot of the device, with
different values of Tx and Cx. Make sure the kernel reports Tx."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
@pytest.mark.skip_if_uhdev( lambda uhdev: "In Range"notin uhdev.fields, "Device not compatible, missing In Range usage",
) def test_mt_inrange(self): """Send one contact that has the InRange bit set before/after
tipswitch.
Kernel is supposed to mark the contact with a distance > 0
when inrange is set but not tipswitch.
This tests the hovering capability of devices (MT_QUIRK_HOVERING).
Make sure the contact is only released from the kernel POV
when the inrange bit is set to 0."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
def test_mt_duplicates(self): """Test the MT_QUIRK_IGNORE_DUPLICATES from the kernel. If a touch is reported more than once with the same Contact ID,
we should only handle the first touch.
Note: this isnotin MS spec, but the current kernel behaves
like that"""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
def test_mt_release_miss(self): """send a single touch in the first slot of the device, and
forget to release it. The kernel is supposed to release by itself
the touch in 100ms.
Make sure that we are dealing with a new touch by resending the
same touch after the timeout expired, and check that the kernel
considers it as a separate touch (different tracking ID)"""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
@pytest.mark.skip_if_uhdev( lambda uhdev: "Azimuth"notin uhdev.fields, "Device not compatible, missing Azimuth usage",
) def test_mt_azimuth(self): """Check for the azimtuh information bit.
When azimuth is presented by the device, it should be exported as ABS_MT_ORIENTATION and the exported value should report a quarter
of circle."""
uhdev = self.uhdev
t0 = Touch(1, 5, 10)
t0.azimuth = 270
r = uhdev.event([t0])
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events)
# orientation is clockwise, while Azimuth is counter clockwise assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_ORIENTATION, 90) in events
class TestPTP(TestWin8Multitouch): def test_ptp_buttons(self): """check for button reliability.
There are 2 types of touchpads: the click pads and the pressure pads.
Each should reliably report the BTN_LEFT events. """
uhdev = self.uhdev
evdev = uhdev.get_evdev()
if uhdev.type == "clickpad":
r = uhdev.event(click=True)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
r = uhdev.event(click=False)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0 else:
r = uhdev.event(left=True)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 1) in events assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 1
r = uhdev.event(left=False)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_LEFT, 0) in events assert evdev.value[libevdev.EV_KEY.BTN_LEFT] == 0
r = uhdev.event(right=True)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 1) in events assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 1
r = uhdev.event(right=False)
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) assert libevdev.InputEvent(libevdev.EV_KEY.BTN_RIGHT, 0) in events assert evdev.value[libevdev.EV_KEY.BTN_RIGHT] == 0
@pytest.mark.skip_if_uhdev( lambda uhdev: "Confidence"notin uhdev.fields, "Device not compatible, missing Confidence usage",
) def test_ptp_confidence(self): """Check for the validity of the confidence bit.
When a contact is marked asnot confident, it should be detected as a palm from the kernel POV and released.
Note: if the kernel exports ABS_MT_TOOL_TYPE, it shouldn't release
the touch but instead convert it to ABS_MT_TOOL_PALM."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
@pytest.mark.skip_if_uhdev( lambda uhdev: uhdev.touches_in_a_report >= uhdev.max_contacts, "Device not compatible, we can not trigger the conditions",
) def test_ptp_non_touch_data(self): """Some single finger hybrid touchpads might not provide the
button information in subsequent reports (only in the first report).
Emulate this and make sure we do not release the buttons in the
middle of the event."""
uhdev = self.uhdev
evdev = uhdev.get_evdev()
touches = [Touch(i, i * 10, i * 10 + 5) for i in range(uhdev.max_contacts)]
contact_count = uhdev.max_contacts
incr_scantime = True
btn_state = True
events = None while touches:
t = touches[: uhdev.touches_in_a_report]
touches = touches[uhdev.touches_in_a_report :]
r = uhdev.event(
t,
click=btn_state,
left=btn_state,
contact_count=contact_count,
incr_scantime=incr_scantime,
)
contact_count = 0
incr_scantime = False
btn_state = False
events = uhdev.next_sync_events()
self.debug_reports(r, uhdev, events) if touches: assert len(events) == 0
for num, r_str in enumerate(sequence):
r = [int(i, 16) for i in r_str.split()]
uhdev.call_input_event(r)
events = uhdev.next_sync_events()
self.debug_reports([r], uhdev) for e in events:
print(e) if num == 2: assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.