diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 34681195..e1090ff4 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -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", diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 37c56bef..b9c7aa7c 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -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 { diff --git a/src/evdev.h b/src/evdev.h index 14253b17..77561577 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -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 diff --git a/test/test-touchpad.c b/test/test-touchpad.c index 2fc9600f..27358f07 100644 --- a/test/test-touchpad.c +++ b/test/test-touchpad.c @@ -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); }