fix(evdev): skip multitouch-only touchpads in keyboard discovery (#917)

* fix(evdev): skip multitouch-only touchpads in keyboard discovery

Devices reporting only ABS_MT_POSITION_X/Y (no single-touch ABS_X/Y)
evaded the pointer-axis check, so multitouch touchpads like the Goodix
GXTP5100 were grabbed and cloned — breaking pointer input while running.

Fixes #914

* fix(evdev): read /proc bitmaps at the kernel's word width

Capability bitmap words are unsigned longs — 32 bits on 32-bit kernels
(armv7l, i686), not 64. Parse the whole bitmap through _bitmap_to_int()
sized by platform.machine() so MT axis bits and any future high bits
land correctly on both widths. Give the fallback MT-mock a real key so
the MT-axes check is the only thing that can drop it.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
Co-authored-by: Jatin K Malik <jk.malik@yahoo.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-07 17:55:25 -07:00
committed by GitHub
co-authored by Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Jatin K Malik Jatin K Malik Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 622d0e054d
commit ecd4ae8388
2 changed files with 121 additions and 24 deletions
+52
View File
@@ -202,6 +202,49 @@ B: REL=0 0 143
result = find_keyboard_devices()
assert result == []
def test_find_keyboard_devices_skips_multitouch_only_touchpad(self) -> None:
"""A touchpad reporting only MT axes (no ABS_X/Y) is a pointer (#914)."""
mock_proc_content = """I: Bus=0018 Vendor=27c6 Product=01e9 Version=0100
N: Name="GXTP5100:00 27C6:01E9"
H: Handlers=mouse0 event2
B: PROP=85
B: EV=1b
B: KEY=10000 0 0 0 0
B: ABS=60000000000000
B: MSC=10
"""
with patch("builtins.open", mock_open(read_data=mock_proc_content)):
with patch("os.path.exists", return_value=True):
result = find_keyboard_devices()
assert result == []
def test_find_keyboard_devices_skips_mixed_single_and_mt_touchpad(self) -> None:
"""A touchpad with both ABS_X/Y and MT axes stays skipped."""
mock_proc_content = """I: Bus=0018 Vendor=27c6 Product=01e9 Version=0100
N: Name="GXTP5100:00 27C6:01E9"
H: Handlers=mouse0 event2
B: KEY=10000 0 0 0 0
B: ABS=60000000000003
"""
with patch("builtins.open", mock_open(read_data=mock_proc_content)):
with patch("os.path.exists", return_value=True):
result = find_keyboard_devices()
assert result == []
def test_find_keyboard_devices_mt_axes_32bit_words(self) -> None:
"""On a 32-bit kernel the same MT axes print as two 32-bit words."""
mock_proc_content = """I: Bus=0018 Vendor=27c6 Product=01e9 Version=0100
N: Name="GXTP5100:00 27C6:01E9"
H: Handlers=mouse0 event2
B: KEY=10000 0 0 0 0
B: ABS=600000 0
"""
with patch("vocalinux.ui.keyboard_backends.evdev_backend._WORD_BITS", 32):
with patch("builtins.open", mock_open(read_data=mock_proc_content)):
with patch("os.path.exists", return_value=True):
result = find_keyboard_devices()
assert result == []
class TestFindKeyboardDevicesFromEvdev:
"""Test _find_keyboard_devices_from_evdev() discovery and filtering."""
@@ -245,6 +288,15 @@ class TestFindKeyboardDevicesFromEvdev:
ecodes.EV_KEY: [ecodes.KEY_A],
ecodes.EV_REL: [ecodes.REL_WHEEL],
},
# Multitouch-only touchpad: MT position axes, no ABS_X/Y (#914).
# Carries a real key so only the MT check can drop it.
"/dev/input/event4": {
ecodes.EV_KEY: [ecodes.KEY_LEFTCTRL, ecodes.BTN_LEFT],
ecodes.EV_ABS: [
(ecodes.ABS_MT_POSITION_X, MagicMock()),
(ecodes.ABS_MT_POSITION_Y, MagicMock()),
],
},
}
devices = self._devices(caps)
mock_evdev.list_devices.return_value = list(caps)