evdev_button_scroll_button(struct evdev_device *device,
			   uint64_t time, int is_press)
{
	/* Where the button lock is enabled, we wrap the buttons into
	   their own little state machine and filter out the events.
	 */
	switch (device->scroll.lock_state) {
	case BUTTONSCROLL_LOCK_DISABLED:
		break;
	case BUTTONSCROLL_LOCK_IDLE:
		assert(is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTDOWN;
		evdev_log_debug(device, "scroll lock: first down\n");
		break; /* handle event */
	case BUTTONSCROLL_LOCK_FIRSTDOWN:
		assert(!is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTUP;
		evdev_log_debug(device, "scroll lock: first up\n");
		return; /* filter release event */
	case BUTTONSCROLL_LOCK_FIRSTUP:
		assert(is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_SECONDDOWN;
		evdev_log_debug(device, "scroll lock: second down\n");
		return; /* filter press event */
	case BUTTONSCROLL_LOCK_SECONDDOWN:
		assert(!is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE;
		evdev_log_debug(device, "scroll lock: idle\n");
		break; /* handle event */
	}

	if (is_press) {
		if (device->scroll.button < BTN_MOUSE + 5) {
			/* For mouse buttons 1-5 (0x110 to 0x114) we apply a timeout before scrolling
			 * since the button could also be used for regular clicking. */
			enum timer_flags flags = TIMER_FLAG_NONE;

			device->scroll.button_scroll_state = BUTTONSCROLL_BUTTON_DOWN;

			/* Special case: if middle button emulation is enabled and
			 * our scroll button is the left or right button, we only
			 * get here *after* the middle button timeout has expired
			 * for that button press. The time passed is the button-down
			 * time though (which is in the past), so we have to allow
			 * for a negative timer to be set.
			 */
			if (device->middlebutton.enabled &&
				(device->scroll.button == BTN_LEFT ||
				device->scroll.button == BTN_RIGHT)) {
				flags = TIMER_FLAG_ALLOW_NEGATIVE;
			}

			libinput_timer_set_flags(&device->scroll.timer,
						time + DEFAULT_BUTTON_SCROLL_TIMEOUT,
						flags);
		} else {
			/* For extra mouse buttons numbered 6 or more (0x115+) we assume it is
			 * dedicated exclusively to scrolling, so we don't apply the timeout
			 * in order to provide immediate scrolling responsiveness. */
			device->scroll.button_scroll_state = BUTTONSCROLL_READY;
		}
		device->scroll.button_down_time = time;
		evdev_log_debug(device, "btnscroll: down\n");
	} else {
		libinput_timer_cancel(&device->scroll.timer);
		switch(device->scroll.button_scroll_state) {
		case BUTTONSCROLL_IDLE:
			evdev_log_bug_libinput(device,
				       "invalid state IDLE for button up\n");
			break;
		case BUTTONSCROLL_BUTTON_DOWN:
		case BUTTONSCROLL_READY:
			evdev_log_debug(device, "btnscroll: cancel\n");

			/* If the button is released quickly enough or
			 * without scroll events, 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);
			break;
		case BUTTONSCROLL_SCROLLING:
			evdev_log_debug(device, "btnscroll: up\n");
			evdev_stop_scroll(device, time,
					  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
			break;
		}

		device->scroll.button_scroll_state = BUTTONSCROLL_IDLE;
	}
}