diff --git a/doc/Makefile.am b/doc/Makefile.am index 31b673b3..a33638da 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,3 +1,5 @@ +EXTRA_DIST = touchpad-tap-state-machine.svg touchpad-softbutton-state-machine.svg + if HAVE_DOXYGEN noinst_DATA = html/index.html @@ -12,7 +14,7 @@ clean-local: $(AM_V_at)rm -rf html doc_src= $(shell find html -type f -printf "html/%P\n" 2>/dev/null) -EXTRA_DIST = $(builddir)/html/index.html $(doc_src) +EXTRA_DIST += $(builddir)/html/index.html $(doc_src) endif diff --git a/doc/touchpad-softbutton-state-machine.svg b/doc/touchpad-softbutton-state-machine.svg new file mode 100644 index 00000000..11423435 --- /dev/null +++ b/doc/touchpad-softbutton-state-machine.svg @@ -0,0 +1,390 @@ + + + + + + + + +NONE + +on-entry: + +curr = none + + + + +BOTTOM_NEW + +on-entry: + +curr = button + +start inner timeout + + + + +AREA + +on-entry: + +curr =area + + + + +finger in + +area + + + + +BOTTOM + + + + +finger + +up + + + + +phys + +button + +press + + + + + + +inner + +timeout + + + + + + + + +finger in + +AREA + + + + + + +BOTTOM_TO_AREA + +on-entry: + +start outer timeout + + + + + + +outer + +timeout + + + + + + + + +finger in + +bottom + + + + + + + + +finger in + +area + + + + + + + + +finger in + +bottom + +button != curr + + + + + + + + + + + + +finger in + +bottom + +button == curr + + + + + + + + + + + + + + + + + + + + + + +Check state of + +all touches + + + + + + + + + +tp_post_softbutton_buttons() + + + + +!buttons.click_pend + +&& current == old + + + + + + +yes + + + + + + +no + + + + + + +current = buttons.state & 0x01 + +old = buttons.old_state & 0x01 + +button = 0 + + + + + + +All touches are in state none + + + + + + +no + + + + + + +yes + + + + +buttons.click_pend = 1 + + + + + + +(Some touches are in right) && + +(Some touches are in left) + + + + + + +yes + + + + +button = BTN_MIDDLE + + + + + + +current + + + + + + +no + + + + + + +yes + + + + +Some touches are in right + + + + + + +yes + + + + + + +no + + + + +button = BTN_RIGHT + + + + +button = BTN_LEFT + + + + + + +no + + + + +buttons.active = button + +state = BUTTON_PRESSED + + + + + + + + + + +button = buttons.active + +buttons.active = 0 + +state = BUTTON_RELEASED + + + + +buttons.click_pend = 0 + + + + + + + + +button + + + + + + +no + + + + + + +yes + + + + +pointer_notify_button( + +button, state) + + + + + + + + + diff --git a/src/Makefile.am b/src/Makefile.am index 471bc1d9..68e02dec 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -14,6 +14,7 @@ libinput_la_SOURCES = \ evdev-mt-touchpad.c \ evdev-mt-touchpad.h \ evdev-mt-touchpad-tap.c \ + evdev-mt-touchpad-buttons.c \ evdev-touchpad.c \ filter.c \ filter.h \ diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c new file mode 100644 index 00000000..65fa21be --- /dev/null +++ b/src/evdev-mt-touchpad-buttons.c @@ -0,0 +1,625 @@ +/* + * Copyright © 2014 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "evdev-mt-touchpad.h" + +#define DEFAULT_BUTTON_MOTION_THRESHOLD 0.02 /* 2% of size */ +#define DEFAULT_BUTTON_ENTER_TIMEOUT 100 /* ms */ +#define DEFAULT_BUTTON_LEAVE_TIMEOUT 300 /* ms */ + +/***************************************** + * BEFORE YOU EDIT THIS FILE, look at the state diagram in + * doc/touchpad-softbutton-state-machine.svg, or online at + * https://drive.google.com/file/d/0B1NwWmji69nocUs1cVJTbkdwMFk/edit?usp=sharing + * (it's a http://draw.io diagram) + * + * Any changes in this file must be represented in the diagram. + * + * The state machine only affects the soft button area code. + */ + +#define CASE_RETURN_STRING(a) case a: return #a; + +static inline const char* +button_state_to_str(enum button_state state) { + switch(state) { + CASE_RETURN_STRING(BUTTON_STATE_NONE); + CASE_RETURN_STRING(BUTTON_STATE_AREA); + CASE_RETURN_STRING(BUTTON_STATE_BOTTOM); + CASE_RETURN_STRING(BUTTON_STATE_BOTTOM_NEW); + CASE_RETURN_STRING(BUTTON_STATE_BOTTOM_TO_AREA); + } + return NULL; +} + +static inline const char* +button_event_to_str(enum button_event event) { + switch(event) { + CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_R); + CASE_RETURN_STRING(BUTTON_EVENT_IN_BOTTOM_L); + CASE_RETURN_STRING(BUTTON_EVENT_IN_AREA); + CASE_RETURN_STRING(BUTTON_EVENT_UP); + CASE_RETURN_STRING(BUTTON_EVENT_PRESS); + CASE_RETURN_STRING(BUTTON_EVENT_RELEASE); + CASE_RETURN_STRING(BUTTON_EVENT_TIMEOUT); + } + return NULL; +} + +static inline bool +is_inside_button_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return t->y >= tp->buttons.area.top_edge; +} + +static inline bool +is_inside_right_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_button_area(tp, t) && + t->x > tp->buttons.area.rightbutton_left_edge; +} + +static inline bool +is_inside_left_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_button_area(tp, t) && + !is_inside_right_area(tp, t); +} + +static void +tp_button_set_timer(struct tp_dispatch *tp, uint64_t timeout) +{ + struct itimerspec its; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + its.it_value.tv_sec = timeout / 1000; + its.it_value.tv_nsec = (timeout % 1000) * 1000 * 1000; + timerfd_settime(tp->buttons.timer_fd, TFD_TIMER_ABSTIME, &its, NULL); +} + +static void +tp_button_set_enter_timer(struct tp_dispatch *tp, struct tp_touch *t) +{ + t->button.timeout = t->millis + DEFAULT_BUTTON_ENTER_TIMEOUT; + tp_button_set_timer(tp, t->button.timeout); +} + +static void +tp_button_set_leave_timer(struct tp_dispatch *tp, struct tp_touch *t) +{ + t->button.timeout = t->millis + DEFAULT_BUTTON_LEAVE_TIMEOUT; + tp_button_set_timer(tp, t->button.timeout); +} + +static void +tp_button_clear_timer(struct tp_dispatch *tp, struct tp_touch *t) +{ + t->button.timeout = 0; +} + +/* + * tp_button_set_state, change state and implement on-entry behavior + * as described in the state machine diagram. + */ +static void +tp_button_set_state(struct tp_dispatch *tp, struct tp_touch *t, + enum button_state new_state, enum button_event event) +{ + tp_button_clear_timer(tp, t); + + t->button.state = new_state; + switch (t->button.state) { + case BUTTON_STATE_NONE: + t->button.curr = 0; + break; + case BUTTON_STATE_AREA: + t->button.curr = BUTTON_EVENT_IN_AREA; + tp_set_pointer(tp, t); + break; + case BUTTON_STATE_BOTTOM: + break; + case BUTTON_STATE_BOTTOM_NEW: + t->button.curr = event; + tp_button_set_enter_timer(tp, t); + break; + case BUTTON_STATE_BOTTOM_TO_AREA: + tp_button_set_leave_timer(tp, t); + break; + } +} + +static void +tp_button_none_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch (event) { + case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_BOTTOM_L: + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, event); + break; + case BUTTON_EVENT_IN_AREA: + tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); + break; + case BUTTON_EVENT_UP: + tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); + break; + case BUTTON_EVENT_PRESS: + case BUTTON_EVENT_RELEASE: + case BUTTON_EVENT_TIMEOUT: + break; + } +} + +static void +tp_button_area_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch (event) { + case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_BOTTOM_L: + case BUTTON_EVENT_IN_AREA: + break; + case BUTTON_EVENT_UP: + tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); + break; + case BUTTON_EVENT_PRESS: + case BUTTON_EVENT_RELEASE: + case BUTTON_EVENT_TIMEOUT: + break; + } +} + +static void +tp_button_bottom_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch (event) { + case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_BOTTOM_L: + if (event != t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, + event); + break; + case BUTTON_EVENT_IN_AREA: + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_TO_AREA, event); + break; + case BUTTON_EVENT_UP: + tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); + break; + case BUTTON_EVENT_PRESS: + case BUTTON_EVENT_RELEASE: + case BUTTON_EVENT_TIMEOUT: + break; + } +} + +static void +tp_button_bottom_new_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch(event) { + case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_BOTTOM_L: + if (event != t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, + event); + break; + case BUTTON_EVENT_IN_AREA: + tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); + break; + case BUTTON_EVENT_UP: + tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); + break; + case BUTTON_EVENT_PRESS: + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event); + break; + case BUTTON_EVENT_RELEASE: + break; + case BUTTON_EVENT_TIMEOUT: + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, event); + break; + } +} + +static void +tp_button_bottom_to_area_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch(event) { + case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_BOTTOM_L: + if (event == t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM, + event); + else + tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, + event); + break; + case BUTTON_EVENT_IN_AREA: + break; + case BUTTON_EVENT_UP: + tp_button_set_state(tp, t, BUTTON_STATE_NONE, event); + break; + case BUTTON_EVENT_PRESS: + case BUTTON_EVENT_RELEASE: + break; + case BUTTON_EVENT_TIMEOUT: + tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); + break; + } +} + +static void +tp_button_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event, + uint64_t time) +{ + enum button_state current = t->button.state; + + switch(t->button.state) { + case BUTTON_STATE_NONE: + tp_button_none_handle_event(tp, t, event); + break; + case BUTTON_STATE_AREA: + tp_button_area_handle_event(tp, t, event); + break; + case BUTTON_STATE_BOTTOM: + tp_button_bottom_handle_event(tp, t, event); + break; + case BUTTON_STATE_BOTTOM_NEW: + tp_button_bottom_new_handle_event(tp, t, event); + break; + case BUTTON_STATE_BOTTOM_TO_AREA: + tp_button_bottom_to_area_handle_event(tp, t, event); + break; + } + + if (current != t->button.state) + log_debug("button state: from %s, event %s to %s\n", + button_state_to_str(current), + button_event_to_str(event), + button_state_to_str(t->button.state)); +} + +int +tp_button_handle_state(struct tp_dispatch *tp, uint64_t time) +{ + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + if (t->state == TOUCH_NONE) + continue; + + if (t->state == TOUCH_END) { + tp_button_handle_event(tp, t, BUTTON_EVENT_UP, time); + } else if (t->dirty) { + if (is_inside_right_area(tp, t)) + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_R, time); + else if (is_inside_left_area(tp, t)) + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_L, time); + else + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_AREA, time); + } + if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE) + tp_button_handle_event(tp, t, BUTTON_EVENT_RELEASE, time); + if (tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) + tp_button_handle_event(tp, t, BUTTON_EVENT_PRESS, time); + } + + return 0; +} + +static void +tp_button_handle_timeout(struct tp_dispatch *tp, uint64_t now) +{ + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + if (t->button.timeout != 0 && t->button.timeout <= now) { + tp_button_clear_timer(tp, t); + tp_button_handle_event(tp, t, BUTTON_EVENT_TIMEOUT, now); + } + } +} + +int +tp_process_button(struct tp_dispatch *tp, + const struct input_event *e, + uint64_t time) +{ + uint32_t mask = 1 << (e->code - BTN_LEFT); + + /* Ignore other buttons on clickpads */ + if (tp->buttons.is_clickpad && e->code != BTN_LEFT) { + log_bug("received %s button event on a clickpad (kernel bug?)\n", + libevdev_event_code_get_name(EV_KEY, e->code)); + return 0; + } + + if (e->value) { + tp->buttons.state |= mask; + tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS; + } else { + tp->buttons.state &= ~mask; + tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE; + } + + return 0; +} + +static void +tp_button_timeout_handler(void *data) +{ + struct tp_dispatch *tp = data; + uint64_t expires; + int len; + struct timespec ts; + uint64_t now; + + len = read(tp->buttons.timer_fd, &expires, sizeof expires); + if (len != sizeof expires) + /* This will only happen if the application made the fd + * non-blocking, but this function should only be called + * upon the timeout, so lets continue anyway. */ + log_error("timerfd read error: %s\n", strerror(errno)); + + clock_gettime(CLOCK_MONOTONIC, &ts); + now = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; + + tp_button_handle_timeout(tp, now); +} + +int +tp_init_buttons(struct tp_dispatch *tp, + struct evdev_device *device) +{ + int width, height; + double diagonal; + + tp->buttons.is_clickpad = libevdev_has_property(device->evdev, + INPUT_PROP_BUTTONPAD); + + if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE) || + libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) { + if (tp->buttons.is_clickpad) + log_bug("clickpad advertising right button (kernel bug?)\n"); + } else { + if (!tp->buttons.is_clickpad) + log_bug("non clickpad without right button (kernel bug)?\n"); + } + + width = abs(device->abs.max_x - device->abs.min_x); + height = abs(device->abs.max_y - device->abs.min_y); + diagonal = sqrt(width*width + height*height); + + tp->buttons.motion_dist = diagonal * DEFAULT_BUTTON_MOTION_THRESHOLD; + + if (libevdev_get_id_vendor(device->evdev) == 0x5ac) /* Apple */ + tp->buttons.use_clickfinger = true; + + if (tp->buttons.is_clickpad && !tp->buttons.use_clickfinger) { + tp->buttons.area.top_edge = height * .8 + device->abs.min_y; + tp->buttons.area.rightbutton_left_edge = width/2 + device->abs.min_x; + tp->buttons.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + + if (tp->buttons.timer_fd == -1) + return -1; + + tp->buttons.source = + libinput_add_fd(tp->device->base.seat->libinput, + tp->buttons.timer_fd, + tp_button_timeout_handler, + tp); + if (tp->buttons.source == NULL) + return -1; + } else { + tp->buttons.area.top_edge = INT_MAX; + } + + return 0; +} + +void +tp_destroy_buttons(struct tp_dispatch *tp) +{ + if (tp->buttons.source) { + libinput_remove_source(tp->device->base.seat->libinput, + tp->buttons.source); + tp->buttons.source = NULL; + } + if (tp->buttons.timer_fd > -1) { + close(tp->buttons.timer_fd); + tp->buttons.timer_fd = -1; + } +} + +static int +tp_post_clickfinger_buttons(struct tp_dispatch *tp, uint64_t time) +{ + uint32_t current, old, button; + enum libinput_pointer_button_state state; + + current = tp->buttons.state; + old = tp->buttons.old_state; + + if (current == old) + return 0; + + if (current) { + switch (tp->nfingers_down) { + case 1: button = BTN_LEFT; break; + case 2: button = BTN_RIGHT; break; + case 3: button = BTN_MIDDLE; break; + default: + return 0; + } + tp->buttons.active = button; + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + } else { + button = tp->buttons.active; + tp->buttons.active = 0; + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + } + + if (button) + pointer_notify_button(&tp->device->base, + time, + button, + state); + return 1; +} + +static int +tp_post_physical_buttons(struct tp_dispatch *tp, uint64_t time) +{ + uint32_t current, old, button; + + current = tp->buttons.state; + old = tp->buttons.old_state; + button = BTN_LEFT; + + while (current || old) { + enum libinput_pointer_button_state state; + + if ((current & 0x1) ^ (old & 0x1)) { + if (!!(current & 0x1)) + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + else + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + + pointer_notify_button(&tp->device->base, + time, + button, + state); + } + + button++; + current >>= 1; + old >>= 1; + } + + return 0; +} + +static int +tp_post_softbutton_buttons(struct tp_dispatch *tp, uint64_t time) +{ + uint32_t current, old, button; + enum libinput_pointer_button_state state; + enum { AREA = 0x01, LEFT = 0x02, RIGHT = 0x04 }; + + current = tp->buttons.state; + old = tp->buttons.old_state; + button = 0; + + if (!tp->buttons.click_pending && current == old) + return 0; + + if (current) { + struct tp_touch *t; + + tp_for_each_touch(tp, t) { + switch (t->button.curr) { + case BUTTON_EVENT_IN_AREA: + button |= AREA; + break; + case BUTTON_EVENT_IN_BOTTOM_L: + button |= LEFT; + break; + case BUTTON_EVENT_IN_BOTTOM_R: + button |= RIGHT; + break; + default: + break; + } + } + + switch (button) { + case 0: + /* No touches, wait for a touch before processing */ + tp->buttons.click_pending = true; + return 0; + case RIGHT: + case RIGHT | AREA: + /* Some touches in right, no touches in left */ + button = BTN_RIGHT; + break; + case LEFT | RIGHT: + case LEFT | RIGHT | AREA: + /* Some touches in left and some in right */ + button = BTN_MIDDLE; + break; + default: + button = BTN_LEFT; + } + + tp->buttons.active = button; + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + } else { + button = tp->buttons.active; + tp->buttons.active = 0; + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + } + + tp->buttons.click_pending = false; + + if (button) + pointer_notify_button(&tp->device->base, + time, + button, + state); + return 1; +} + +int +tp_post_button_events(struct tp_dispatch *tp, uint64_t time) +{ + if (tp->buttons.is_clickpad) { + if (tp->buttons.use_clickfinger) + return tp_post_clickfinger_buttons(tp, time); + else + return tp_post_softbutton_buttons(tp, time); + } + + return tp_post_physical_buttons(tp, time); +} + +int +tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t) +{ + return t->button.state == BUTTON_STATE_AREA; +} diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 5fa712f6..eee334f2 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -97,7 +97,7 @@ tap_event_to_str(enum tap_event event) { static void tp_tap_notify(struct tp_dispatch *tp, - uint32_t time, + uint64_t time, int nfingers, enum libinput_pointer_button_state state) { @@ -118,9 +118,9 @@ tp_tap_notify(struct tp_dispatch *tp, } static void -tp_tap_set_timer(struct tp_dispatch *tp, uint32_t time) +tp_tap_set_timer(struct tp_dispatch *tp, uint64_t time) { - uint32_t timeout = time + DEFAULT_TAP_TIMEOUT_PERIOD; + uint64_t timeout = time + DEFAULT_TAP_TIMEOUT_PERIOD; struct itimerspec its; its.it_interval.tv_sec = 0; @@ -139,7 +139,7 @@ tp_tap_clear_timer(struct tp_dispatch *tp) } static void -tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -160,7 +160,7 @@ tp_tap_idle_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t } static void -tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -185,7 +185,7 @@ tp_tap_touch_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t } static void -tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -206,7 +206,7 @@ tp_tap_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t } static void -tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -230,7 +230,7 @@ tp_tap_tapped_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_ } static void -tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -256,7 +256,7 @@ tp_tap_touch2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_ } static void -tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -278,7 +278,7 @@ tp_tap_touch2_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, ui } static void -tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -303,7 +303,7 @@ tp_tap_touch3_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_ } static void -tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -324,7 +324,7 @@ tp_tap_touch3_hold_handle_event(struct tp_dispatch *tp, enum tap_event event, ui } static void -tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { case TAP_EVENT_TOUCH: @@ -349,7 +349,7 @@ tp_tap_dragging_or_doubletap_handle_event(struct tp_dispatch *tp, enum tap_event } static void -tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -372,7 +372,7 @@ tp_tap_dragging_handle_event(struct tp_dispatch *tp, enum tap_event event, uint3 } static void -tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -395,7 +395,7 @@ tp_tap_dragging_wait_handle_event(struct tp_dispatch *tp, enum tap_event event, } static void -tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -418,7 +418,7 @@ tp_tap_dragging2_handle_event(struct tp_dispatch *tp, enum tap_event event, uint } static void -tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { switch (event) { @@ -435,7 +435,7 @@ tp_tap_dead_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t } static void -tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint32_t time) +tp_tap_handle_event(struct tp_dispatch *tp, enum tap_event event, uint64_t time) { enum tp_tap_state current; if (!tp->tap.enabled) @@ -503,7 +503,7 @@ tp_tap_exceeds_motion_threshold(struct tp_dispatch *tp, struct tp_touch *t) } int -tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time) +tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; int filter_motion = 0; @@ -554,7 +554,7 @@ tp_tap_timeout_handler(void *data) uint64_t expires; int len; struct timespec ts; - uint32_t now; + uint64_t now; len = read(touchpad->tap.timer_fd, &expires, sizeof expires); if (len != sizeof expires) @@ -564,13 +564,13 @@ tp_tap_timeout_handler(void *data) log_error("timerfd read error: %s\n", strerror(errno)); clock_gettime(CLOCK_MONOTONIC, &ts); - now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + now = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; tp_tap_handle_timeout(touchpad, now); } unsigned int -tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time) +tp_tap_handle_timeout(struct tp_dispatch *tp, uint64_t time) { if (!tp->tap.enabled) return 0; @@ -615,6 +615,8 @@ tp_destroy_tap(struct tp_dispatch *tp) libinput_remove_source(tp->device->base.seat->libinput, tp->tap.source); tp->tap.source = NULL; } - if (tp->tap.timer_fd > -1) + if (tp->tap.timer_fd > -1) { close(tp->tap.timer_fd); + tp->tap.timer_fd = -1; + } } diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index 6c63cb6c..89cebd55 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -51,7 +51,7 @@ static double tp_accel_profile(struct motion_filter *filter, void *data, double velocity, - uint32_t time) + uint64_t time) { struct tp_dispatch *tp = (struct tp_dispatch *) data; @@ -80,7 +80,7 @@ tp_motion_history_offset(struct tp_touch *t, int offset) static void tp_filter_motion(struct tp_dispatch *tp, - double *dx, double *dy, uint32_t time) + double *dx, double *dy, uint64_t time) { struct motion_params motion; @@ -152,24 +152,14 @@ tp_get_touch(struct tp_dispatch *tp, unsigned int slot) static inline void tp_begin_touch(struct tp_dispatch *tp, struct tp_touch *t) { - struct tp_touch *tmp = NULL; - if (t->state != TOUCH_UPDATE) { tp_motion_history_reset(t); t->dirty = true; t->state = TOUCH_BEGIN; + t->pinned.is_pinned = false; tp->nfingers_down++; assert(tp->nfingers_down >= 1); tp->queued |= TOUCHPAD_EVENT_MOTION; - - tp_for_each_touch(tp, tmp) { - if (tmp->is_pointer) - break; - } - - if (!tmp->is_pointer) { - t->is_pointer = true; - } } } @@ -182,6 +172,7 @@ tp_end_touch(struct tp_dispatch *tp, struct tp_touch *t) t->dirty = true; t->is_pointer = false; t->state = TOUCH_END; + t->pinned.is_pinned = false; assert(tp->nfingers_down >= 1); tp->nfingers_down--; tp->queued |= TOUCHPAD_EVENT_MOTION; @@ -215,7 +206,7 @@ tp_get_delta(struct tp_touch *t, double *dx, double *dy) static void tp_process_absolute(struct tp_dispatch *tp, const struct input_event *e, - uint32_t time) + uint64_t time) { struct tp_touch *t = tp_current_touch(tp); @@ -247,7 +238,7 @@ tp_process_absolute(struct tp_dispatch *tp, static void tp_process_absolute_st(struct tp_dispatch *tp, const struct input_event *e, - uint32_t time) + uint64_t time) { struct tp_touch *t = tp_current_touch(tp); @@ -270,7 +261,7 @@ tp_process_absolute_st(struct tp_dispatch *tp, static void tp_process_fake_touch(struct tp_dispatch *tp, const struct input_event *e, - uint32_t time) + uint64_t time) { struct tp_touch *t; unsigned int fake_touches; @@ -318,22 +309,13 @@ tp_process_fake_touch(struct tp_dispatch *tp, static void tp_process_key(struct tp_dispatch *tp, const struct input_event *e, - uint32_t time) + uint64_t time) { - uint32_t mask; - switch (e->code) { case BTN_LEFT: case BTN_MIDDLE: case BTN_RIGHT: - mask = 1 << (e->code - BTN_LEFT); - if (e->value) { - tp->buttons.state |= mask; - tp->queued |= TOUCHPAD_EVENT_BUTTON_PRESS; - } else { - tp->buttons.state &= ~mask; - tp->queued |= TOUCHPAD_EVENT_BUTTON_RELEASE; - } + tp_process_button(tp, e, time); break; case BTN_TOUCH: case BTN_TOOL_DOUBLETAP: @@ -346,54 +328,60 @@ tp_process_key(struct tp_dispatch *tp, } static void -tp_unpin_finger(struct tp_dispatch *tp) +tp_unpin_finger(struct tp_dispatch *tp, struct tp_touch *t) +{ + unsigned int xdist, ydist; + + if (!t->pinned.is_pinned) + return; + + xdist = abs(t->x - t->pinned.center_x); + ydist = abs(t->y - t->pinned.center_y); + + if (xdist * xdist + ydist * ydist >= + tp->buttons.motion_dist * tp->buttons.motion_dist) { + t->pinned.is_pinned = false; + tp_set_pointer(tp, t); + } +} + +static void +tp_pin_fingers(struct tp_dispatch *tp) { struct tp_touch *t; - tp_for_each_touch(tp, t) { - if (t->is_pinned) { - t->is_pinned = false; - if (t->state != TOUCH_END && - tp->nfingers_down == 1) - t->is_pointer = true; - break; - } + tp_for_each_touch(tp, t) { + t->is_pointer = false; + t->pinned.is_pinned = true; + t->pinned.center_x = t->x; + t->pinned.center_y = t->y; } } -static void -tp_pin_finger(struct tp_dispatch *tp) +static int +tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t) { - struct tp_touch *t, - *pinned = NULL; + return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) && + !t->pinned.is_pinned && tp_button_touch_active(tp, t); +} - tp_for_each_touch(tp, t) { - if (t->is_pinned) { - pinned = t; - break; - } +void +tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t) +{ + struct tp_touch *tmp = NULL; + + /* Only set the touch as pointer if we don't have one yet */ + tp_for_each_touch(tp, tmp) { + if (tmp->is_pointer) + return; } - assert(!pinned); - - pinned = tp_current_touch(tp); - - if (tp->nfingers_down != 1) { - tp_for_each_touch(tp, t) { - if (t == pinned) - continue; - - if (t->y > pinned->y) - pinned = t; - } - } - - pinned->is_pinned = true; - pinned->is_pointer = false; + if (tp_touch_active(tp, t)) + t->is_pointer = true; } static void -tp_process_state(struct tp_dispatch *tp, uint32_t time) +tp_process_state(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; struct tp_touch *first = tp_get_touch(tp, 0); @@ -409,20 +397,25 @@ tp_process_state(struct tp_dispatch *tp, uint32_t time) tp_motion_hysteresis(tp, t); tp_motion_history_push(t); + + tp_unpin_finger(tp, t); } - /* We have a physical button down event on a clickpad. For drag and - drop, this means we try to identify which finger pressed the - physical button and "pin" it, i.e. remove pointer-moving - capabilities from it. + tp_button_handle_state(tp, time); + + /* + * We have a physical button down event on a clickpad. To avoid + * spurious pointer moves by the clicking finger we pin all fingers. + * We unpin fingers when they move more then a certain threshold to + * to allow drag and drop. */ if ((tp->queued & TOUCHPAD_EVENT_BUTTON_PRESS) && - !tp->buttons.has_buttons) - tp_pin_finger(tp); + tp->buttons.is_clickpad) + tp_pin_fingers(tp); } static void -tp_post_process_state(struct tp_dispatch *tp, uint32_t time) +tp_post_process_state(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; @@ -441,14 +434,11 @@ tp_post_process_state(struct tp_dispatch *tp, uint32_t time) tp->buttons.old_state = tp->buttons.state; - if (tp->queued & TOUCHPAD_EVENT_BUTTON_RELEASE) - tp_unpin_finger(tp); - tp->queued = TOUCHPAD_EVENT_NONE; } static void -tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) +tp_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t; int nchanged = 0; @@ -456,7 +446,7 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) double tmpx, tmpy; tp_for_each_touch(tp, t) { - if (t->dirty) { + if (tp_touch_active(tp, t) && t->dirty) { nchanged++; tp_get_delta(t, &tmpx, &tmpy); @@ -507,14 +497,18 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint32_t time) } static int -tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time) +tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time) { - /* don't scroll if a clickpad is held down */ - if (!tp->buttons.has_buttons && - (tp->buttons.state || tp->buttons.old_state)) - return 0; + struct tp_touch *t; + int nfingers_down = 0; - if (tp->nfingers_down != 2) { + /* Only count active touches for 2 finger scrolling */ + tp_for_each_touch(tp, t) { + if (tp_touch_active(tp, t)) + nfingers_down++; + } + + if (nfingers_down != 2) { /* terminate scrolling with a zero scroll event to notify * caller that it really ended now */ if (tp->scroll.state != SCROLL_STATE_NONE) { @@ -538,89 +532,8 @@ tp_post_scroll_events(struct tp_dispatch *tp, uint32_t time) return 0; } -static int -tp_post_clickfinger_buttons(struct tp_dispatch *tp, uint32_t time) -{ - uint32_t current, old, button; - enum libinput_pointer_button_state state; - - current = tp->buttons.state; - old = tp->buttons.old_state; - - if (current == old) - return 0; - - switch (tp->nfingers_down) { - case 1: button = BTN_LEFT; break; - case 2: button = BTN_RIGHT; break; - case 3: button = BTN_MIDDLE; break; - default: - return 0; - } - - if (current) - state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; - else - state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; - - pointer_notify_button(&tp->device->base, - time, - button, - state); - return 1; -} - -static int -tp_post_physical_buttons(struct tp_dispatch *tp, uint32_t time) -{ - uint32_t current, old, button; - - current = tp->buttons.state; - old = tp->buttons.old_state; - button = BTN_LEFT; - - while (current || old) { - enum libinput_pointer_button_state state; - - if ((current & 0x1) ^ (old & 0x1)) { - if (!!(current & 0x1)) - state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; - else - state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; - - pointer_notify_button(&tp->device->base, - time, - button, - state); - } - - button++; - current >>= 1; - old >>= 1; - } - - return 0; -} - -static int -tp_post_button_events(struct tp_dispatch *tp, uint32_t time) -{ - int rc; - - if ((tp->queued & - (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0) - return 0; - - if (tp->buttons.has_buttons) - rc = tp_post_physical_buttons(tp, time); - else - rc = tp_post_clickfinger_buttons(tp, time); - - return rc; -} - static void -tp_post_events(struct tp_dispatch *tp, uint32_t time) +tp_post_events(struct tp_dispatch *tp, uint64_t time) { struct tp_touch *t = tp_current_touch(tp); double dx, dy; @@ -661,7 +574,7 @@ static void tp_process(struct evdev_dispatch *dispatch, struct evdev_device *device, struct input_event *e, - uint32_t time) + uint64_t time) { struct tp_dispatch *tp = (struct tp_dispatch *)dispatch; @@ -691,6 +604,7 @@ tp_destroy(struct evdev_dispatch *dispatch) (struct tp_dispatch*)dispatch; tp_destroy_tap(tp); + tp_destroy_buttons(tp); if (tp->filter) tp->filter->interface->destroy(tp->filter); @@ -703,10 +617,18 @@ static struct evdev_dispatch_interface tp_interface = { tp_destroy }; +static void +tp_init_touch(struct tp_dispatch *tp, + struct tp_touch *t) +{ + t->button.state = BUTTON_STATE_NONE; +} + static int tp_init_slots(struct tp_dispatch *tp, struct evdev_device *device) { + size_t i; const struct input_absinfo *absinfo; absinfo = libevdev_get_abs_info(device->evdev, ABS_MT_SLOT); @@ -715,15 +637,38 @@ tp_init_slots(struct tp_dispatch *tp, tp->slot = absinfo->value; tp->has_mt = true; } else { - tp->ntouches = 5; /* FIXME: based on DOUBLETAP, etc. */ + struct map { + unsigned int code; + int ntouches; + } max_touches[] = { + { BTN_TOOL_QUINTTAP, 5 }, + { BTN_TOOL_QUADTAP, 4 }, + { BTN_TOOL_TRIPLETAP, 3 }, + { BTN_TOOL_DOUBLETAP, 2 }, + }; + struct map *m; + tp->slot = 0; tp->has_mt = false; + tp->ntouches = 1; + + ARRAY_FOR_EACH(max_touches, m) { + if (libevdev_has_event_code(device->evdev, + EV_KEY, + m->code)) { + tp->ntouches = m->ntouches; + break; + } + } } tp->touches = calloc(tp->ntouches, sizeof(struct tp_touch)); if (!tp->touches) return -1; + for (i = 0; i < tp->ntouches; i++) + tp_init_touch(tp, &tp->touches[i]); + return 0; } @@ -765,6 +710,7 @@ tp_init(struct tp_dispatch *tp, tp->base.interface = &tp_interface; tp->device = device; tp->tap.timer_fd = -1; + tp->buttons.timer_fd = -1; if (tp_init_slots(tp, device) != 0) return -1; @@ -778,10 +724,6 @@ tp_init(struct tp_dispatch *tp, tp->hysteresis.margin_y = diagonal / DEFAULT_HYSTERESIS_MARGIN_DENOMINATOR; - if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE) || - libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) - tp->buttons.has_buttons = true; - if (tp_init_scroll(tp) != 0) return -1; @@ -791,6 +733,9 @@ tp_init(struct tp_dispatch *tp, if (tp_init_tap(tp) != 0) return -1; + if (tp_init_buttons(tp, device) != 0) + return -1; + return 0; } diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 2bdb3295..41e3ca43 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -46,6 +46,24 @@ enum touch_state { TOUCH_END }; +enum button_event { + BUTTON_EVENT_IN_BOTTOM_R = 30, + BUTTON_EVENT_IN_BOTTOM_L, + BUTTON_EVENT_IN_AREA, + BUTTON_EVENT_UP, + BUTTON_EVENT_PRESS, + BUTTON_EVENT_RELEASE, + BUTTON_EVENT_TIMEOUT, +}; + +enum button_state { + BUTTON_STATE_NONE, + BUTTON_STATE_AREA, + BUTTON_STATE_BOTTOM, + BUTTON_STATE_BOTTOM_NEW, + BUTTON_STATE_BOTTOM_TO_AREA, +}; + enum scroll_state { SCROLL_STATE_NONE, SCROLL_STATE_SCROLLING @@ -77,10 +95,9 @@ struct tp_touch { bool dirty; bool fake; /* a fake touch */ bool is_pointer; /* the pointer-controlling touch */ - bool is_pinned; /* holds the phys. button */ int32_t x; int32_t y; - uint32_t millis; + uint64_t millis; struct { struct tp_motion samples[TOUCHPAD_HISTORY_LENGTH]; @@ -92,6 +109,24 @@ struct tp_touch { int32_t center_x; int32_t center_y; } hysteresis; + + /* A pinned touchpoint is the one that pressed the physical button + * on a clickpad. After the release, it won't move until the center + * moves more than a threshold away from the original coordinates + */ + struct { + bool is_pinned; + int32_t center_x; + int32_t center_y; + } pinned; + + /* Software-button state and timeout if applicable */ + struct { + enum button_state state; + /* We use button_event here so we can use == on events */ + enum button_event curr; + uint64_t timeout; + } button; }; struct tp_dispatch { @@ -119,9 +154,28 @@ struct tp_dispatch { } accel; struct { - bool has_buttons; /* true for physical LMR buttons */ + bool is_clickpad; /* true for clickpads */ + bool use_clickfinger; /* number of fingers decides button number */ + bool click_pending; uint32_t state; uint32_t old_state; + uint32_t motion_dist; /* for pinned touches */ + unsigned int active; /* currently active button, for release event */ + + /* Only used for clickpads. The software button area is always + * a horizontal strip across the touchpad. Depending on the + * rightbutton_left_edge value, the buttons are split according to the + * edge settings. + */ + struct { + int32_t top_edge; + int32_t rightbutton_left_edge; + } area; + + unsigned int timeout; /* current timeout in ms */ + + int timer_fd; + struct libinput_source *source; } buttons; /* physical buttons */ struct { @@ -146,11 +200,14 @@ struct tp_dispatch { void tp_get_delta(struct tp_touch *t, double *dx, double *dy); +void +tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t); + int -tp_tap_handle_state(struct tp_dispatch *tp, uint32_t time); +tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time); unsigned int -tp_tap_handle_timeout(struct tp_dispatch *tp, uint32_t time); +tp_tap_handle_timeout(struct tp_dispatch *tp, uint64_t time); int tp_init_tap(struct tp_dispatch *tp); @@ -158,4 +215,24 @@ tp_init_tap(struct tp_dispatch *tp); void tp_destroy_tap(struct tp_dispatch *tp); +int +tp_init_buttons(struct tp_dispatch *tp, struct evdev_device *device); + +void +tp_destroy_buttons(struct tp_dispatch *tp); + +int +tp_process_button(struct tp_dispatch *tp, + const struct input_event *e, + uint64_t time); + +int +tp_post_button_events(struct tp_dispatch *tp, uint64_t time); + +int +tp_button_handle_state(struct tp_dispatch *tp, uint64_t time); + +int +tp_button_touch_active(struct tp_dispatch *tp, struct tp_touch *t); + #endif diff --git a/src/evdev-touchpad.c b/src/evdev-touchpad.c index 1a484419..18b63530 100644 --- a/src/evdev-touchpad.c +++ b/src/evdev-touchpad.c @@ -211,7 +211,7 @@ static double touchpad_profile(struct motion_filter *filter, void *data, double velocity, - uint32_t time) + uint64_t time) { struct touchpad_dispatch *touchpad = (struct touchpad_dispatch *) data; @@ -273,7 +273,7 @@ touchpad_get_delta(struct touchpad_dispatch *touchpad, double *dx, double *dy) static void filter_motion(struct touchpad_dispatch *touchpad, - double *dx, double *dy, uint32_t time) + double *dx, double *dy, uint64_t time) { struct motion_params motion; @@ -287,7 +287,7 @@ filter_motion(struct touchpad_dispatch *touchpad, } static void -notify_button_pressed(struct touchpad_dispatch *touchpad, uint32_t time) +notify_button_pressed(struct touchpad_dispatch *touchpad, uint64_t time) { pointer_notify_button( &touchpad->device->base, @@ -297,7 +297,7 @@ notify_button_pressed(struct touchpad_dispatch *touchpad, uint32_t time) } static void -notify_button_released(struct touchpad_dispatch *touchpad, uint32_t time) +notify_button_released(struct touchpad_dispatch *touchpad, uint64_t time) { pointer_notify_button( &touchpad->device->base, @@ -307,16 +307,16 @@ notify_button_released(struct touchpad_dispatch *touchpad, uint32_t time) } static void -notify_tap(struct touchpad_dispatch *touchpad, uint32_t time) +notify_tap(struct touchpad_dispatch *touchpad, uint64_t time) { notify_button_pressed(touchpad, time); notify_button_released(touchpad, time); } static void -process_fsm_events(struct touchpad_dispatch *touchpad, uint32_t time) +process_fsm_events(struct touchpad_dispatch *touchpad, uint64_t time) { - uint32_t timeout = UINT32_MAX; + uint64_t timeout = UINT64_MAX; enum fsm_event event; unsigned int i; @@ -398,7 +398,7 @@ process_fsm_events(struct touchpad_dispatch *touchpad, uint32_t time) } } - if (timeout != UINT32_MAX) { + if (timeout != UINT64_MAX) { struct itimerspec its; its.it_interval.tv_sec = 0; @@ -447,7 +447,7 @@ fsm_timeout_handler(void *data) uint64_t expires; int len; struct timespec ts; - uint32_t now; + uint64_t now; len = read(touchpad->fsm.timer.fd, &expires, sizeof expires); if (len != sizeof expires) @@ -458,7 +458,7 @@ fsm_timeout_handler(void *data) if (touchpad->fsm.events_count == 0) { clock_gettime(CLOCK_MONOTONIC, &ts); - now = ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + now = ts.tv_sec * 1000ULL + ts.tv_nsec / 1000000; push_fsm_event(touchpad, FSM_EVENT_TIMEOUT); process_fsm_events(touchpad, now); @@ -466,7 +466,7 @@ fsm_timeout_handler(void *data) } static void -touchpad_update_state(struct touchpad_dispatch *touchpad, uint32_t time) +touchpad_update_state(struct touchpad_dispatch *touchpad, uint64_t time) { int motion_index; int center_x, center_y; @@ -618,7 +618,7 @@ static inline void process_key(struct touchpad_dispatch *touchpad, struct evdev_device *device, struct input_event *e, - uint32_t time) + uint64_t time) { uint32_t code; @@ -685,7 +685,7 @@ static void touchpad_process(struct evdev_dispatch *dispatch, struct evdev_device *device, struct input_event *e, - uint32_t time) + uint64_t time) { struct touchpad_dispatch *touchpad = (struct touchpad_dispatch *) dispatch; diff --git a/src/evdev.c b/src/evdev.c index 57dcca76..08a18fd8 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -110,7 +110,7 @@ evdev_device_transform_y(struct evdev_device *device, } static void -evdev_flush_pending_event(struct evdev_device *device, uint32_t time) +evdev_flush_pending_event(struct evdev_device *device, uint64_t time) { int32_t cx, cy; li_fixed_t x, y; @@ -309,7 +309,7 @@ evdev_process_key(struct evdev_device *device, struct input_event *e, int time) static void evdev_process_touch(struct evdev_device *device, struct input_event *e, - uint32_t time) + uint64_t time) { switch (e->code) { case ABS_MT_SLOT: @@ -358,7 +358,7 @@ evdev_process_absolute_motion(struct evdev_device *device, static inline void evdev_process_relative(struct evdev_device *device, - struct input_event *e, uint32_t time) + struct input_event *e, uint64_t time) { struct libinput_device *base = &device->base; @@ -406,7 +406,7 @@ evdev_process_relative(struct evdev_device *device, static inline void evdev_process_absolute(struct evdev_device *device, struct input_event *e, - uint32_t time) + uint64_t time) { if (device->is_mt) { evdev_process_touch(device, e, time); @@ -441,7 +441,7 @@ static void fallback_process(struct evdev_dispatch *dispatch, struct evdev_device *device, struct input_event *event, - uint32_t time) + uint64_t time) { int need_frame = 0; @@ -491,7 +491,7 @@ static inline void evdev_process_event(struct evdev_device *device, struct input_event *e) { struct evdev_dispatch *dispatch = device->dispatch; - uint32_t time = e->time.tv_sec * 1000 + e->time.tv_usec / 1000; + uint64_t time = e->time.tv_sec * 1000ULL + e->time.tv_usec / 1000; dispatch->interface->process(dispatch, device, e, time); } diff --git a/src/evdev.h b/src/evdev.h index 36daf3c7..bb88074e 100644 --- a/src/evdev.h +++ b/src/evdev.h @@ -101,7 +101,7 @@ struct evdev_dispatch_interface { void (*process)(struct evdev_dispatch *dispatch, struct evdev_device *device, struct input_event *event, - uint32_t time); + uint64_t time); /* Destroy an event dispatch handler and free all its resources. */ void (*destroy)(struct evdev_dispatch *dispatch); diff --git a/src/filter.c b/src/filter.c index 397e0f63..2b1a6755 100644 --- a/src/filter.c +++ b/src/filter.c @@ -32,7 +32,7 @@ void filter_dispatch(struct motion_filter *filter, struct motion_params *motion, - void *data, uint32_t time) + void *data, uint64_t time) { filter->interface->filter(filter, motion, data, time); } @@ -48,7 +48,7 @@ filter_dispatch(struct motion_filter *filter, struct pointer_tracker { double dx; double dy; - uint32_t time; + uint64_t time; int dir; }; @@ -128,7 +128,7 @@ get_direction(int dx, int dy) static void feed_trackers(struct pointer_accelerator *accel, double dx, double dy, - uint32_t time) + uint64_t time) { int i, current; struct pointer_tracker *trackers = accel->trackers; @@ -157,7 +157,7 @@ tracker_by_offset(struct pointer_accelerator *accel, unsigned int offset) } static double -calculate_tracker_velocity(struct pointer_tracker *tracker, uint32_t time) +calculate_tracker_velocity(struct pointer_tracker *tracker, uint64_t time) { int dx; int dy; @@ -170,7 +170,7 @@ calculate_tracker_velocity(struct pointer_tracker *tracker, uint32_t time) } static double -calculate_velocity(struct pointer_accelerator *accel, uint32_t time) +calculate_velocity(struct pointer_accelerator *accel, uint64_t time) { struct pointer_tracker *tracker; double velocity; @@ -224,14 +224,14 @@ calculate_velocity(struct pointer_accelerator *accel, uint32_t time) static double acceleration_profile(struct pointer_accelerator *accel, - void *data, double velocity, uint32_t time) + void *data, double velocity, uint64_t time) { return accel->profile(&accel->base, data, velocity, time); } static double calculate_acceleration(struct pointer_accelerator *accel, - void *data, double velocity, uint32_t time) + void *data, double velocity, uint64_t time) { double factor; @@ -273,7 +273,7 @@ apply_softening(struct pointer_accelerator *accel, static void accelerator_filter(struct motion_filter *filter, struct motion_params *motion, - void *data, uint32_t time) + void *data, uint64_t time) { struct pointer_accelerator *accel = (struct pointer_accelerator *) filter; diff --git a/src/filter.h b/src/filter.h index 6b2a1d20..0ef3d032 100644 --- a/src/filter.h +++ b/src/filter.h @@ -34,13 +34,13 @@ struct motion_filter; void filter_dispatch(struct motion_filter *filter, struct motion_params *motion, - void *data, uint32_t time); + void *data, uint64_t time); struct motion_filter_interface { void (*filter)(struct motion_filter *filter, struct motion_params *motion, - void *data, uint32_t time); + void *data, uint64_t time); void (*destroy)(struct motion_filter *filter); }; @@ -54,7 +54,7 @@ create_linear_acceleration_filter(double speed); typedef double (*accel_profile_func_t)(struct motion_filter *filter, void *data, double velocity, - uint32_t time); + uint64_t time); struct motion_filter * create_pointer_accelator_filter(accel_profile_func_t filter); diff --git a/src/libinput.h b/src/libinput.h index 85c7d717..d771e21c 100644 --- a/src/libinput.h +++ b/src/libinput.h @@ -38,6 +38,46 @@ extern "C" { * behind an API. */ +/** + * @page tpbuttons Touchpad button behavior + * + * For touchpad devices without physical buttons, libinput enables an + * emulated right button area through either of two methods. + * + * Software button areas + * ===================== + * On most touchpads, the bottom area of the touchpad is split into a a left + * and a right-button area. Pressing the touchpad down with a finger in + * those areas will generate clicks as shown in the diagram below: + * + * @code + +------------------------+ + | | + | | + | LEFT | + | | + | | + +------------------------+ + | LEFT | RIGHT | + +------------------------+ + * @endcode + * + * Generally, the touchpad will emulate a right-button click if the finger + * was set down in the right button area and did not leave the + * right button area before clicking, even if another finger was already + * down on the touchpad in another area. + * A middle click is generated by clicking the touchpad when one finger is + * in the bottom left button area, and one finger is in the botton right + * button area. + * The exact behavior of the touchpad is implementation-dependent. + * + * Clickfinger + * =========== + * On Apple touchpads, no button areas are provided. Instead, use a + * two-finger click for a right button click, and a three-finger click for a + * middle button click. + */ + /** * @ingroup fixed_point * diff --git a/test/litest-bcm5974.c b/test/litest-bcm5974.c index 25d59ac2..43605aaf 100644 --- a/test/litest-bcm5974.c +++ b/test/litest-bcm5974.c @@ -92,6 +92,7 @@ static int events[] = { EV_KEY, BTN_TOOL_DOUBLETAP, EV_KEY, BTN_TOOL_TRIPLETAP, EV_KEY, BTN_TOOL_QUADTAP, + INPUT_PROP_MAX, INPUT_PROP_BUTTONPAD, -1, -1 }; diff --git a/test/touchpad.c b/test/touchpad.c index f4d78391..959978e5 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -70,10 +70,10 @@ START_TEST(touchpad_2fg_no_motion) litest_drain_events(li); - litest_touch_down(dev, 0, 50, 50); - litest_touch_down(dev, 1, 70, 70); - litest_touch_move_to(dev, 0, 50, 50, 80, 50, 5); - litest_touch_move_to(dev, 1, 70, 70, 80, 50, 5); + litest_touch_down(dev, 0, 20, 20); + litest_touch_down(dev, 1, 70, 20); + litest_touch_move_to(dev, 0, 20, 20, 80, 80, 5); + litest_touch_move_to(dev, 1, 70, 20, 80, 50, 5); litest_touch_up(dev, 0); litest_touch_up(dev, 1); @@ -217,7 +217,7 @@ END_TEST START_TEST(touchpad_1fg_clickfinger) { - struct litest_device *dev = litest_current_device(); + struct litest_device *dev = litest_create_device(LITEST_BCM5974); struct libinput *li = dev->libinput; struct libinput_event *event; struct libinput_event_pointer *ptrev; @@ -237,12 +237,14 @@ START_TEST(touchpad_1fg_clickfinger) LIBINPUT_POINTER_BUTTON_STATE_PRESSED); assert_button_event(li, BTN_LEFT, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + + litest_delete_device(dev); } END_TEST START_TEST(touchpad_2fg_clickfinger) { - struct litest_device *dev = litest_current_device(); + struct litest_device *dev = litest_create_device(LITEST_BCM5974); struct libinput *li = dev->libinput; struct libinput_event *event; struct libinput_event_pointer *ptrev; @@ -264,6 +266,8 @@ START_TEST(touchpad_2fg_clickfinger) LIBINPUT_POINTER_BUTTON_STATE_PRESSED); assert_button_event(li, BTN_RIGHT, LIBINPUT_POINTER_BUTTON_STATE_RELEASED); + + litest_delete_device(dev); } END_TEST @@ -362,8 +366,8 @@ int main(int argc, char **argv) { litest_add("touchpad:tap", touchpad_1fg_tap_n_drag, LITEST_TOUCHPAD, LITEST_ANY); litest_add("touchpad:tap", touchpad_2fg_tap, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); - litest_add("touchpad:clickfinger", touchpad_1fg_clickfinger, LITEST_TOUCHPAD, LITEST_ANY); - litest_add("touchpad:clickfinger", touchpad_2fg_clickfinger, LITEST_TOUCHPAD, LITEST_SINGLE_TOUCH); + litest_add_no_device("touchpad:clickfinger", touchpad_1fg_clickfinger); + litest_add_no_device("touchpad:clickfinger", touchpad_2fg_clickfinger); litest_add("touchpad:click", touchpad_btn_left, LITEST_TOUCHPAD, LITEST_CLICKPAD); litest_add("touchpad:click", clickpad_btn_left, LITEST_CLICKPAD, LITEST_ANY);