touchpad: don't disable on external mice until we see an event

Instead of disabling the touchpad as soon as a mouse is seen by
libinput, disable it as soon as a mouse sends an actual event. This
works around the current issues with many devices (touchpads and
keyboards alike) announcing a HID Mouse Application Collection which
gets its own event node in the kernel. That event node usually just sits
there and does nothing but its mere presence disabled the touchpad.

Let's change this and instead only disable once we see an event.

Closes: #1104
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1414>
This commit is contained in:
Peter Hutterer 2026-01-21 09:09:14 +10:00 committed by Marge Bot
parent 89351c715a
commit 33b1d87c08
4 changed files with 127 additions and 35 deletions

View file

@ -45,6 +45,8 @@
#define FAKE_FINGER_OVERFLOW bit(7)
#define THUMB_IGNORE_SPEED_THRESHOLD 20 /* mm/s */
#define MOUSE_HAS_SENT_EVENTS bit(1)
enum notify {
DONT_NOTIFY,
DO_NOTIFY,
@ -1918,7 +1920,7 @@ tp_interface_process(struct evdev_dispatch *dispatch,
static void
tp_remove_sendevents(struct tp_dispatch *tp)
{
struct evdev_paired_device *kbd;
struct evdev_paired_device *kbd, *mouse;
libinput_timer_cancel(&tp->palm.trackpoint_timer);
libinput_timer_cancel(&tp->dwt.keyboard_timer);
@ -1930,6 +1932,10 @@ tp_remove_sendevents(struct tp_dispatch *tp)
libinput_device_remove_event_listener(&kbd->listener);
}
list_for_each_safe(mouse, &tp->sendevents.external_mice_list, link) {
evdev_paired_device_destroy(mouse);
}
if (tp->lid_switch.lid_switch)
libinput_device_remove_event_listener(&tp->lid_switch.listener);
@ -2530,24 +2536,60 @@ tp_pair_tablet(struct evdev_device *touchpad, struct evdev_device *tablet)
}
}
static void
tp_external_mouse_event(usec_t time, struct libinput_event *event, void *data)
{
struct tp_dispatch *tp = data;
if (event->type < LIBINPUT_EVENT_POINTER_MOTION ||
event->type >= LIBINPUT_EVENT_TOUCH_DOWN)
return;
struct libinput_device *libinput_device = libinput_event_get_device(event);
struct evdev_device *device = (struct evdev_device *)libinput_device;
struct evdev_paired_device *paired;
list_for_each(paired, &tp->sendevents.external_mice_list, link) {
if (paired->device == device) {
paired->flags |= MOUSE_HAS_SENT_EVENTS;
/* In theory we should be waiting for a neutral state here but
* that's hopefully niche enough. tp_suspend() clears our state
* anyway.
*/
if (tp->sendevents.current_mode ==
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)
tp_suspend(tp, tp->device, SUSPEND_EXTERNAL_MOUSE);
break;
}
}
}
static void
tp_pair_external_mouse(struct evdev_device *touchpad, struct evdev_device *mouse)
{
struct tp_dispatch *tp = (struct tp_dispatch *)touchpad->dispatch;
if (!(mouse->tags & EVDEV_TAG_EXTERNAL_MOUSE))
return;
struct evdev_paired_device *paired = zalloc(sizeof(*paired));
paired->device = mouse;
libinput_device_add_event_listener(&mouse->base,
&paired->listener,
tp_external_mouse_event,
tp);
list_insert(&tp->sendevents.external_mice_list, &paired->link);
}
static void
tp_interface_device_added(struct evdev_device *device,
struct evdev_device *added_device)
{
struct tp_dispatch *tp = (struct tp_dispatch *)device->dispatch;
tp_pair_trackpoint(device, added_device);
tp_dwt_pair_keyboard(device, added_device);
tp_pair_lid_switch(device, added_device);
tp_pair_tablet_mode_switch(device, added_device);
tp_pair_tablet(device, added_device);
if (tp->sendevents.current_mode !=
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE)
return;
if (added_device->tags & EVDEV_TAG_EXTERNAL_MOUSE)
tp_suspend(tp, device, SUSPEND_EXTERNAL_MOUSE);
tp_pair_external_mouse(device, added_device);
}
static void
@ -2555,7 +2597,7 @@ tp_interface_device_removed(struct evdev_device *device,
struct evdev_device *removed_device)
{
struct tp_dispatch *tp = (struct tp_dispatch *)device->dispatch;
struct evdev_paired_device *kbd;
struct evdev_paired_device *kbd, *mouse;
if (removed_device == tp->buttons.trackpoint) {
/* Clear any pending releases for the trackpoint */
@ -2589,21 +2631,18 @@ tp_interface_device_removed(struct evdev_device *device,
tp_resume(tp, device, SUSPEND_TABLET_MODE);
}
if (tp->sendevents.current_mode ==
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE) {
struct libinput_device *dev;
bool found = false;
list_for_each(dev, &device->base.seat->devices_list, link) {
struct evdev_device *d = evdev_device(dev);
if (d != removed_device &&
(d->tags & EVDEV_TAG_EXTERNAL_MOUSE)) {
found = true;
break;
}
bool have_external_mouse_sending_events = false;
list_for_each_safe(mouse, &tp->sendevents.external_mice_list, link) {
if (mouse->device == removed_device) {
evdev_paired_device_destroy(mouse);
} else if (mouse->flags & MOUSE_HAS_SENT_EVENTS) {
have_external_mouse_sending_events = true;
}
if (!found)
tp_resume(tp, device, SUSPEND_EXTERNAL_MOUSE);
}
if (tp->sendevents.current_mode ==
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE &&
!have_external_mouse_sending_events) {
tp_resume(tp, device, SUSPEND_EXTERNAL_MOUSE);
}
if (removed_device == tp->left_handed.tablet_device) {
@ -3468,6 +3507,8 @@ tp_init_sendevents(struct tp_dispatch *tp, struct evdev_device *device)
{
char timer_name[64];
list_init(&tp->sendevents.external_mice_list);
snprintf(timer_name,
sizeof(timer_name),
"%s trackpoint",

View file

@ -482,6 +482,8 @@ struct tp_dispatch {
struct {
struct libinput_device_config_send_events config;
enum libinput_config_send_events_mode current_mode;
struct list external_mice_list;
} sendevents;
struct {

View file

@ -1015,6 +1015,7 @@ struct evdev_paired_device {
struct list link;
struct evdev_device *device;
struct libinput_event_listener listener;
uint32_t flags; /* generic flags used by the caller */
};
static inline void

View file

@ -5817,6 +5817,7 @@ START_TEST(touchpad_disabled_on_mouse)
bool suspend = litest_test_param_get_bool(test_env->params, "suspend");
litest_drain_events(li);
litest_disable_hold_gestures(dev->libinput_device);
status = libinput_device_config_send_events_set_mode(
dev->libinput_device,
@ -5831,6 +5832,18 @@ START_TEST(touchpad_disabled_on_mouse)
mouse = litest_add_device(li, LITEST_MOUSE);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
/* Mouse hasn't sent events yet */
litest_touch_down(dev, 0, 20, 30);
litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10);
litest_touch_up(dev, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
/* Now send the events */
litest_event(mouse, EV_REL, REL_X, -1);
litest_event(mouse, EV_REL, REL_Y, 1);
litest_event(mouse, EV_SYN, SYN_REPORT, 0);
litest_drain_events(li);
litest_touch_down(dev, 0, 20, 30);
litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10);
litest_touch_up(dev, 0);
@ -5862,12 +5875,15 @@ END_TEST
START_TEST(touchpad_disabled_double_mouse)
{
struct litest_device *dev = litest_current_device();
struct litest_device *mouse1, *mouse2;
struct litest_device *nonsending_mouse, *sending_mouse;
struct libinput *li = dev->libinput;
enum libinput_config_status status;
bool suspend = litest_test_param_get_bool(test_env->params, "suspend");
bool suspend_nonsending =
litest_test_param_get_bool(test_env->params, "suspend-nonsending");
int32_t remove = litest_test_param_get_i32(test_env->params, "remove");
litest_drain_events(li);
litest_disable_hold_gestures(dev->libinput_device);
status = libinput_device_config_send_events_set_mode(
dev->libinput_device,
@ -5879,19 +5895,26 @@ START_TEST(touchpad_disabled_double_mouse)
litest_touch_up(dev, 0);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
mouse1 = litest_add_device(li, LITEST_MOUSE);
mouse2 = litest_add_device(li, LITEST_MOUSE_LOW_DPI);
nonsending_mouse = litest_add_device(li, LITEST_MOUSE);
sending_mouse = litest_add_device(li, LITEST_MOUSE_LOW_DPI);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_ADDED);
/* Mouse hasn't sent events yet */
litest_touch_down(dev, 0, 20, 30);
litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
if (suspend) {
/* Now send the events */
litest_event(sending_mouse, EV_REL, REL_X, -1);
litest_event(sending_mouse, EV_REL, REL_Y, 1);
litest_event(sending_mouse, EV_SYN, SYN_REPORT, 0);
litest_drain_events(li);
if (suspend_nonsending) {
/* Disable one external mouse -> don't expect touchpad events */
status = libinput_device_config_send_events_set_mode(
mouse1->libinput_device,
nonsending_mouse->libinput_device,
LIBINPUT_CONFIG_SEND_EVENTS_DISABLED);
litest_assert_enum_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS);
}
@ -5901,15 +5924,34 @@ START_TEST(touchpad_disabled_double_mouse)
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
litest_device_destroy(mouse1);
switch (remove) {
case 1:
litest_device_destroy(steal(&nonsending_mouse));
break;
case 2:
litest_device_destroy(steal(&sending_mouse));
break;
}
litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
litest_touch_down(dev, 0, 20, 30);
litest_touch_move_to(dev, 0, 20, 30, 90, 30, 10);
litest_touch_up(dev, 0);
litest_assert_empty_queue(li);
litest_device_destroy(mouse2);
/* Removing the only mouse that sent events should resume our touchpad */
switch (remove) {
case 1:
litest_assert_empty_queue(li);
break;
case 2:
litest_assert_only_typed_events(li, LIBINPUT_EVENT_POINTER_MOTION);
break;
}
if (sending_mouse)
litest_device_destroy(steal(&sending_mouse));
if (nonsending_mouse)
litest_device_destroy(steal(&nonsending_mouse));
litest_assert_only_typed_events(li, LIBINPUT_EVENT_DEVICE_REMOVED);
litest_touch_down(dev, 0, 20, 30);
@ -7072,6 +7114,12 @@ TEST_COLLECTION(touchpad)
litest_with_parameters(params, "suspend", 'b') {
litest_add_parametrized_for_device(touchpad_disabled_on_mouse, LITEST_SYNAPTICS_CLICKPAD_X220, params);
}
litest_with_parameters(params,
"suspend-nonsending", 'b',
"remove", 'I', 2, litest_named_i32(2, "sending-mouse"),
litest_named_i32(1, "nonsending-mouse")) {
litest_add_parametrized_for_device(touchpad_disabled_double_mouse, LITEST_SYNAPTICS_CLICKPAD_X220, params);
}