[PR #47] [MERGED] fix(touch): don't reuse an active MT slot when placing a new finger #48

Closed
opened 2026-10-15 12:51:36 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/games-on-whales/inputtino/pull/47
Author: @c127dev
Created: 7/11/2026
Status: ✅ Merged
Merged: 7/13/2026
Merged by: @ABeltramo

Base: stable ← Head: fix/touchscreen-place-finger-overwritten


📝 Commits (1)

  • 309c68e fix(touch): don't reuse an active MT slot when placing a new finger

📊 Changes

4 files changed (+99 additions, -15 deletions)

View changed files

📝 src/uinput/include/inputtino/protected_types.hpp (+25 -2)
📝 src/uinput/touchscreen.cpp (+16 -5)
📝 src/uinput/trackpad.cpp (+16 -5)
📝 tests/testLibinput.cpp (+42 -3)

📄 Description

Problem

While streaming a game from Sunshine to an Android device with Moonlight (touch passthrough), multi-touch gestures would occasionally glitch: two distinct fingers were treated as the same contact. With two fingers down, lifting one and placing it again made both fingers share a single MT slot — their coordinates alternated inside one contact, so the remaining finger appeared to "teleport" between the two positions.

Root cause chain

  1. Slot allocation (TouchScreen::place_finger() / Trackpad::place_finger()): a new finger was assigned the slot fingers.size() + 1. This is only correct when fingers are released in reverse order of placement. Sequence that breaks it: place finger A → slot 1, place finger B → slot 2, release A (fingers.size() is now 1), place A again → slot 1 + 1 = 2 — the same slot finger B is still using. Two finger_ids now map to one MT slot.

  2. Tracking ID reuse: ABS_MT_TRACKING_ID was set to the slot number itself. In the evdev MT protocol the tracking id must uniquely identify a contact for its lifetime; reusing slot numbers as tracking ids means a re-placed finger can resurrect the identity of a previous contact instead of starting a new one.

  3. current_slot desync: when a new finger selected its slot via ABS_MT_SLOT, current_slot was not updated, so a following event for another finger could skip the ABS_MT_SLOT write and land in the wrong slot. release_finger() also reset current_slot to -1 after emitting ABS_MT_SLOT, even though the kernel's slot state remained at the released slot.

  4. release_finger() on unknown ids: it used _state->fingers[finger_nr], so releasing a finger id that was never placed default-constructed an entry mapping it to slot 0 and emitted MT_TRACKING_ID = -1 there — killing an unrelated active contact.

Trackpad shared the exact same allocation logic, so it had the same bug.

Fix

  • Assign new contacts the lowest free MT slot (first_free_mt_slot()) instead of fingers.size() + 1. As a side effect slots are now 0-based, matching the evdev convention.
  • Use a monotonically increasing ABS_MT_TRACKING_ID (wrapping at 65536) instead of reusing the slot number, so every new contact has a unique identity.
  • Keep current_slot in sync: set it when a new contact selects its slot, and track the actual kernel slot state in release_finger() instead of resetting to -1.
  • release_finger() now ignores unknown finger ids instead of implicitly mapping them to slot 0 and corrupting another contact.
  • Apply all of the above to both TouchScreen and Trackpad.

Verified

  • Extended tests/testLibinput.cpp to cover the exact failure sequence: two fingers down, first one lifted and placed again — it must get its own free slot back (slot 0) while the second finger keeps being tracked in its own slot (slot 1). All libinput tests pass.
  • Recompiled Sunshine against patched inputtino and reproduced the original scenario from an Android phone over Moonlight: repeated lift-and-replace multi-touch gestures no longer merge contacts, both fingers track correctly.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/games-on-whales/inputtino/pull/47 **Author:** [@c127dev](https://github.com/c127dev) **Created:** 7/11/2026 **Status:** ✅ Merged **Merged:** 7/13/2026 **Merged by:** [@ABeltramo](https://github.com/ABeltramo) **Base:** `stable` ← **Head:** `fix/touchscreen-place-finger-overwritten` --- ### 📝 Commits (1) - [`309c68e`](https://github.com/games-on-whales/inputtino/commit/309c68e2c9d323a59a5f1ef80ebfba63e02de6f5) fix(touch): don't reuse an active MT slot when placing a new finger ### 📊 Changes **4 files changed** (+99 additions, -15 deletions) <details> <summary>View changed files</summary> 📝 `src/uinput/include/inputtino/protected_types.hpp` (+25 -2) 📝 `src/uinput/touchscreen.cpp` (+16 -5) 📝 `src/uinput/trackpad.cpp` (+16 -5) 📝 `tests/testLibinput.cpp` (+42 -3) </details> ### 📄 Description ## Problem While streaming a game from Sunshine to an Android device with Moonlight (touch passthrough), multi-touch gestures would occasionally glitch: two distinct fingers were treated as the same contact. With two fingers down, lifting one and placing it again made both fingers share a single MT slot — their coordinates alternated inside one contact, so the remaining finger appeared to "teleport" between the two positions. ## Root cause chain 1. **Slot allocation** (`TouchScreen::place_finger()` / `Trackpad::place_finger()`): a new finger was assigned the slot `fingers.size() + 1`. This is only correct when fingers are released in reverse order of placement. Sequence that breaks it: place finger A → slot 1, place finger B → slot 2, release A (`fingers.size()` is now 1), place A again → slot `1 + 1 = 2` — **the same slot finger B is still using**. Two `finger_id`s now map to one MT slot. 2. **Tracking ID reuse**: `ABS_MT_TRACKING_ID` was set to the slot number itself. In the evdev MT protocol the tracking id must uniquely identify a contact for its lifetime; reusing slot numbers as tracking ids means a re-placed finger can resurrect the identity of a previous contact instead of starting a new one. 3. **`current_slot` desync**: when a new finger selected its slot via `ABS_MT_SLOT`, `current_slot` was not updated, so a following event for another finger could skip the `ABS_MT_SLOT` write and land in the wrong slot. `release_finger()` also reset `current_slot` to `-1` after emitting `ABS_MT_SLOT`, even though the kernel's slot state remained at the released slot. 4. **`release_finger()` on unknown ids**: it used `_state->fingers[finger_nr]`, so releasing a finger id that was never placed default-constructed an entry mapping it to slot 0 and emitted `MT_TRACKING_ID = -1` there — killing an unrelated active contact. `Trackpad` shared the exact same allocation logic, so it had the same bug. ## Fix - Assign new contacts the **lowest free MT slot** (`first_free_mt_slot()`) instead of `fingers.size() + 1`. As a side effect slots are now 0-based, matching the evdev convention. - Use a **monotonically increasing `ABS_MT_TRACKING_ID`** (wrapping at 65536) instead of reusing the slot number, so every new contact has a unique identity. - Keep `current_slot` in sync: set it when a new contact selects its slot, and track the actual kernel slot state in `release_finger()` instead of resetting to `-1`. - `release_finger()` now ignores unknown finger ids instead of implicitly mapping them to slot 0 and corrupting another contact. - Apply all of the above to both `TouchScreen` and `Trackpad`. ## Verified - Extended `tests/testLibinput.cpp` to cover the exact failure sequence: two fingers down, first one lifted and placed again — it must get its own free slot back (slot 0) while the second finger keeps being tracked in its own slot (slot 1). All libinput tests pass. - Recompiled Sunshine against patched inputtino and reproduced the original scenario from an Android phone over Moonlight: repeated lift-and-replace multi-touch gestures no longer merge contacts, both fingers track correctly. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 12:51:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
games-on-whales/inputtino#48
No description provided.