diff --git a/src/evdev.c b/src/evdev.c index 6bb8986a..861a2987 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -154,14 +154,17 @@ evdev_pointer_notify_physical_button(struct evdev_device *device, state)) return; - evdev_pointer_notify_button(device, time, button, state); + evdev_pointer_notify_button(device, + time, + (unsigned int)button, + state); } -void -evdev_pointer_notify_button(struct evdev_device *device, - uint64_t time, - int button, - enum libinput_button_state state) +static void +evdev_pointer_post_button(struct evdev_device *device, + uint64_t time, + unsigned int button, + enum libinput_button_state state) { int down_count; @@ -182,6 +185,59 @@ evdev_pointer_notify_button(struct evdev_device *device, } +static void +evdev_button_scroll_timeout(uint64_t time, void *data) +{ + struct evdev_device *device = data; + + device->scroll.button_scroll_active = true; +} + +static void +evdev_button_scroll_button(struct evdev_device *device, + uint64_t time, int is_press) +{ + device->scroll.button_scroll_btn_pressed = is_press; + + if (is_press) { + libinput_timer_set(&device->scroll.timer, + time + DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT); + device->scroll.button_down_time = time; + } else { + libinput_timer_cancel(&device->scroll.timer); + if (device->scroll.button_scroll_active) { + evdev_stop_scroll(device, time, + LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS); + device->scroll.button_scroll_active = false; + } else { + /* If the button is released quickly enough emit the + * button press/release events. */ + evdev_pointer_post_button(device, + device->scroll.button_down_time, + device->scroll.button, + LIBINPUT_BUTTON_STATE_PRESSED); + evdev_pointer_post_button(device, time, + device->scroll.button, + LIBINPUT_BUTTON_STATE_RELEASED); + } + } +} + +void +evdev_pointer_notify_button(struct evdev_device *device, + uint64_t time, + unsigned int button, + enum libinput_button_state state) +{ + if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN && + button == device->scroll.button) { + evdev_button_scroll_button(device, time, state); + return; + } + + evdev_pointer_post_button(device, time, button, state); +} + void evdev_device_led_update(struct evdev_device *device, enum libinput_led leds) { @@ -273,13 +329,15 @@ evdev_post_trackpoint_scroll(struct evdev_device *device, uint64_t time) { if (device->scroll.method != LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN || - !hw_is_key_down(device, device->scroll.button)) + !device->scroll.button_scroll_btn_pressed) return false; if (device->scroll.button_scroll_active) evdev_post_scroll(device, time, LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS, &unaccel); + /* if the button is down but scroll is not active, we're within the + timeout where swallow motion events but don't post scroll buttons */ return true; } @@ -480,42 +538,6 @@ get_key_type(uint16_t code) return EVDEV_KEY_TYPE_NONE; } -static void -evdev_button_scroll_timeout(uint64_t time, void *data) -{ - struct evdev_device *device = data; - - device->scroll.button_scroll_active = true; -} - -static void -evdev_button_scroll_button(struct evdev_device *device, - uint64_t time, int is_press) -{ - if (is_press) { - libinput_timer_set(&device->scroll.timer, - time + DEFAULT_MIDDLE_BUTTON_SCROLL_TIMEOUT); - device->scroll.button_down_time = time; - } else { - libinput_timer_cancel(&device->scroll.timer); - if (device->scroll.button_scroll_active) { - evdev_stop_scroll(device, time, - LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS); - device->scroll.button_scroll_active = false; - } else { - /* If the button is released quickly enough emit the - * button press/release events. */ - evdev_pointer_notify_physical_button(device, - device->scroll.button_down_time, - device->scroll.button, - LIBINPUT_BUTTON_STATE_PRESSED); - evdev_pointer_notify_physical_button(device, time, - device->scroll.button, - LIBINPUT_BUTTON_STATE_RELEASED); - } - } -} - static void evdev_process_touch_button(struct evdev_device *device, uint64_t time, int value) @@ -576,11 +598,6 @@ evdev_process_key(struct evdev_device *device, LIBINPUT_KEY_STATE_RELEASED); break; case EVDEV_KEY_TYPE_BUTTON: - if (device->scroll.method == LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN && - e->code == device->scroll.button) { - evdev_button_scroll_button(device, time, e->value); - break; - } evdev_pointer_notify_physical_button( device, time, diff --git a/src/evdev.h b/src/evdev.h index 4a5d807d..76eeb8c2 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -172,6 +172,7 @@ struct evdev_device { /* Checks if buttons are down and commits the setting */ void (*change_scroll_method)(struct evdev_device *device); bool button_scroll_active; + bool button_scroll_btn_pressed; double threshold; double direction_lock_threshold; uint32_t direction; @@ -397,7 +398,7 @@ evdev_keyboard_notify_key(struct evdev_device *device, void evdev_pointer_notify_button(struct evdev_device *device, uint64_t time, - int button, + unsigned int button, enum libinput_button_state state); void evdev_pointer_notify_physical_button(struct evdev_device *device, diff --git a/test/pointer.c b/test/pointer.c index a34740a2..ea1157e5 100644 --- a/test/pointer.c +++ b/test/pointer.c @@ -893,6 +893,88 @@ START_TEST(pointer_scroll_button) } END_TEST +START_TEST(pointer_scroll_button_no_event_before_timeout) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + int i; + + disable_button_scrolling(device); + + libinput_device_config_scroll_set_method(device->libinput_device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + libinput_device_config_scroll_set_button(device->libinput_device, + BTN_LEFT); + litest_drain_events(li); + + litest_button_click(device, BTN_LEFT, true); + litest_assert_empty_queue(li); + + for (i = 0; i < 10; i++) { + litest_event(device, EV_REL, REL_Y, 1); + litest_event(device, EV_SYN, SYN_REPORT, 0); + } + litest_assert_empty_queue(li); + + litest_timeout_buttonscroll(); + libinput_dispatch(li); + litest_button_click(device, BTN_LEFT, false); + litest_assert_empty_queue(li); +} +END_TEST + +START_TEST(pointer_scroll_button_middle_emulation) +{ + struct litest_device *dev = litest_current_device(); + struct libinput_device *device = dev->libinput_device; + struct libinput *li = dev->libinput; + enum libinput_config_status status; + int i; + + status = libinput_device_config_middle_emulation_set_enabled(device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + status = libinput_device_config_scroll_set_method(device, + LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + status = libinput_device_config_scroll_set_button(device, BTN_MIDDLE); + ck_assert_int_eq(status, LIBINPUT_CONFIG_STATUS_SUCCESS); + + litest_drain_events(li); + + litest_button_click(dev, BTN_LEFT, 1); + litest_button_click(dev, BTN_RIGHT, 1); + libinput_dispatch(li); + litest_timeout_buttonscroll(); + libinput_dispatch(li); + + for (i = 0; i < 10; i++) { + litest_event(dev, EV_REL, REL_Y, -1); + litest_event(dev, EV_SYN, SYN_REPORT, 0); + } + + libinput_dispatch(li); + + litest_button_click(dev, BTN_LEFT, 0); + litest_button_click(dev, BTN_RIGHT, 0); + libinput_dispatch(li); + + litest_assert_scroll(li, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL, -1); + litest_assert_empty_queue(li); + + /* Restore default scroll behavior */ + libinput_device_config_scroll_set_method(dev->libinput_device, + libinput_device_config_scroll_get_default_method( + dev->libinput_device)); + libinput_device_config_scroll_set_button(dev->libinput_device, + libinput_device_config_scroll_get_default_button( + dev->libinput_device)); +} +END_TEST + START_TEST(pointer_scroll_nowheel_defaults) { struct litest_device *dev = litest_current_device(); @@ -1208,6 +1290,62 @@ START_TEST(middlebutton) } END_TEST +START_TEST(middlebutton_nostart_while_down) +{ + struct litest_device *device = litest_current_device(); + struct libinput *li = device->libinput; + enum libinput_config_status status; + unsigned int i; + const int btn[][4] = { + { BTN_LEFT, BTN_RIGHT, BTN_LEFT, BTN_RIGHT }, + { BTN_LEFT, BTN_RIGHT, BTN_RIGHT, BTN_LEFT }, + { BTN_RIGHT, BTN_LEFT, BTN_LEFT, BTN_RIGHT }, + { BTN_RIGHT, BTN_LEFT, BTN_RIGHT, BTN_LEFT }, + }; + + if (!libinput_device_pointer_has_button(device->libinput_device, + BTN_MIDDLE)) + return; + + disable_button_scrolling(device); + + status = libinput_device_config_middle_emulation_set_enabled( + device->libinput_device, + LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + if (status == LIBINPUT_CONFIG_STATUS_UNSUPPORTED) + return; + + litest_button_click(device, BTN_MIDDLE, true); + litest_drain_events(li); + + for (i = 0; i < ARRAY_LENGTH(btn); i++) { + litest_button_click(device, btn[i][0], true); + litest_assert_button_event(li, + btn[i][0], + LIBINPUT_BUTTON_STATE_PRESSED); + litest_button_click(device, btn[i][1], true); + litest_assert_button_event(li, + btn[i][1], + LIBINPUT_BUTTON_STATE_PRESSED); + + litest_assert_empty_queue(li); + + litest_button_click(device, btn[i][2], false); + litest_assert_button_event(li, + btn[i][2], + LIBINPUT_BUTTON_STATE_RELEASED); + litest_button_click(device, btn[i][3], false); + litest_assert_button_event(li, + btn[i][3], + LIBINPUT_BUTTON_STATE_RELEASED); + litest_assert_empty_queue(li); + } + + litest_button_click(device, BTN_MIDDLE, false); + litest_drain_events(li); +} +END_TEST + START_TEST(middlebutton_timeout) { struct litest_device *device = litest_current_device(); @@ -1600,6 +1738,8 @@ litest_setup_tests(void) litest_add_no_device("pointer:button", pointer_seat_button_count); litest_add("pointer:scroll", pointer_scroll_wheel, LITEST_WHEEL, LITEST_TABLET); litest_add("pointer:scroll", pointer_scroll_button, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_no_event_before_timeout, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:scroll", pointer_scroll_button_middle_emulation, LITEST_RELATIVE|LITEST_BUTTON, LITEST_ANY); litest_add("pointer:scroll", pointer_scroll_nowheel_defaults, LITEST_RELATIVE|LITEST_BUTTON, LITEST_WHEEL); litest_add("pointer:scroll", pointer_scroll_natural_defaults, LITEST_WHEEL, LITEST_TABLET); litest_add("pointer:scroll", pointer_scroll_natural_enable_config, LITEST_WHEEL, LITEST_TABLET); @@ -1624,6 +1764,7 @@ litest_setup_tests(void) litest_add("pointer:accel", pointer_accel_profile_flat_motion_relative, LITEST_RELATIVE, LITEST_TOUCHPAD); litest_add("pointer:middlebutton", middlebutton, LITEST_BUTTON, LITEST_ANY); + litest_add("pointer:middlebutton", middlebutton_nostart_while_down, LITEST_BUTTON, LITEST_ANY); litest_add("pointer:middlebutton", middlebutton_timeout, LITEST_BUTTON, LITEST_ANY); litest_add("pointer:middlebutton", middlebutton_doubleclick, LITEST_BUTTON, LITEST_ANY); litest_add("pointer:middlebutton", middlebutton_middleclick, LITEST_BUTTON, LITEST_ANY);