From 9cf548e770a814ac53379ecf75886c4ef318538a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 27 May 2014 15:08:35 +0200 Subject: [PATCH] touchpad: Add support for top softbutton area Add support for the top softbutton area found on some laptops. For details of how this works, see the updated doc/touchpad-softbutton-state-machine.svg diagram. Basically this mirrors the state-machine for the bottom softbutton area, with one exception, if a finger stays at least inner timeout milliseconds in the top button area and then moves out of it, it will be ignored rather then become the pointer. This is done so that people using the top buttons together with a trackstick and accidentally move their finger out of the upper area don't get spurious pointer movements from the finger on the trackpad. This behavior is indentical to xf86-input-synaptics, which also ignores movements from touches which start in the top button area. Signed-off-by: Hans de Goede Reviewed-by: Peter Hutterer Signed-off-by: Peter Hutterer --- doc/touchpad-softbutton-state-machine.svg | 604 +++++++++++++--------- src/evdev-mt-touchpad-buttons.c | 247 ++++++++- src/evdev-mt-touchpad.h | 21 +- 3 files changed, 621 insertions(+), 251 deletions(-) diff --git a/doc/touchpad-softbutton-state-machine.svg b/doc/touchpad-softbutton-state-machine.svg index 11423435..05254d0e 100644 --- a/doc/touchpad-softbutton-state-machine.svg +++ b/doc/touchpad-softbutton-state-machine.svg @@ -1,390 +1,532 @@ - + - - - + + + - + NONE - + on-entry: - + curr = none - + - + BOTTOM_NEW - + on-entry: - + curr = button - -start inner timeout + +start enter timeout - + - + AREA - + on-entry: - + curr =area + +set_pointer() - + - + finger in - -area + +area or top - + - + BOTTOM - + - + finger - + up - + - + phys - + button - + press - - - + + + - -inner - + +enter + timeout - - - - - + + + + + - + finger in - -AREA + +area or top - - - + + + - + BOTTOM_TO_AREA - + on-entry: - -start outer timeout + +start leave timeout - - - + + + - -outer - + +leave + 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) + +(Some touches are in middle) || + +((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) - - - - - - + + + + + + + + + +finger in + +top + + + + + + + +TOP_NEW + +on-entry: + +curr = button + +start enter timeout + + + + + + + +TOP + + + + + +phys + +button + +press + + + + +enter + +timeout + + + + +finger in + +top + +button != curr + + + + + + + + + + + + + + + + + + +finger in + +area or + +bottom + + + + + + + + +TOP_TO_IGNORE + +on-entry: + +start leave timeout + + + + +finger in + +area or bottom + + + + +finger in + +top + +button == curr + + + + + + + + + + + + + + +IGNORE + +on-entry: + +curr =none + + + + +leave + +timeout + + + + + + + + + diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index fa6d4a79..faf04b78 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -31,6 +31,10 @@ #include "evdev-mt-touchpad.h" +#ifndef INPUT_PROP_TOPBUTTONPAD +#define INPUT_PROP_TOPBUTTONPAD 0x04 +#endif + #define DEFAULT_BUTTON_MOTION_THRESHOLD 0.02 /* 2% of size */ #define DEFAULT_BUTTON_ENTER_TIMEOUT 100 /* ms */ #define DEFAULT_BUTTON_LEAVE_TIMEOUT 300 /* ms */ @@ -56,6 +60,10 @@ button_state_to_str(enum button_state state) { CASE_RETURN_STRING(BUTTON_STATE_BOTTOM); CASE_RETURN_STRING(BUTTON_STATE_BOTTOM_NEW); CASE_RETURN_STRING(BUTTON_STATE_BOTTOM_TO_AREA); + CASE_RETURN_STRING(BUTTON_STATE_TOP); + CASE_RETURN_STRING(BUTTON_STATE_TOP_NEW); + CASE_RETURN_STRING(BUTTON_STATE_TOP_TO_IGNORE); + CASE_RETURN_STRING(BUTTON_STATE_IGNORE); } return NULL; } @@ -65,6 +73,9 @@ 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_TOP_R); + CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_M); + CASE_RETURN_STRING(BUTTON_EVENT_IN_TOP_L); CASE_RETURN_STRING(BUTTON_EVENT_IN_AREA); CASE_RETURN_STRING(BUTTON_EVENT_UP); CASE_RETURN_STRING(BUTTON_EVENT_PRESS); @@ -94,6 +105,34 @@ is_inside_bottom_left_area(struct tp_dispatch *tp, struct tp_touch *t) !is_inside_bottom_right_area(tp, t); } +static inline bool +is_inside_top_button_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return t->y <= tp->buttons.top_area.bottom_edge; +} + +static inline bool +is_inside_top_right_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_top_button_area(tp, t) && + t->x > tp->buttons.top_area.rightbutton_left_edge; +} + +static inline bool +is_inside_top_left_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_top_button_area(tp, t) && + t->x < tp->buttons.top_area.leftbutton_right_edge; +} + +static inline bool +is_inside_top_middle_area(struct tp_dispatch *tp, struct tp_touch *t) +{ + return is_inside_top_button_area(tp, t) && + t->x >= tp->buttons.top_area.leftbutton_right_edge && + t->x <= tp->buttons.top_area.rightbutton_left_edge; +} + static void tp_button_set_timer(struct tp_dispatch *tp, uint64_t timeout) { @@ -153,6 +192,18 @@ tp_button_set_state(struct tp_dispatch *tp, struct tp_touch *t, case BUTTON_STATE_BOTTOM_TO_AREA: tp_button_set_leave_timer(tp, t); break; + case BUTTON_STATE_TOP: + break; + case BUTTON_STATE_TOP_NEW: + t->button.curr = event; + tp_button_set_enter_timer(tp, t); + break; + case BUTTON_STATE_TOP_TO_IGNORE: + tp_button_set_leave_timer(tp, t); + break; + case BUTTON_STATE_IGNORE: + t->button.curr = 0; + break; } } @@ -166,6 +217,11 @@ tp_button_none_handle_event(struct tp_dispatch *tp, case BUTTON_EVENT_IN_BOTTOM_L: tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, event); break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: + tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, event); + break; case BUTTON_EVENT_IN_AREA: tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); break; @@ -187,6 +243,9 @@ tp_button_area_handle_event(struct tp_dispatch *tp, switch (event) { case BUTTON_EVENT_IN_BOTTOM_R: case BUTTON_EVENT_IN_BOTTOM_L: + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: case BUTTON_EVENT_IN_AREA: break; case BUTTON_EVENT_UP: @@ -211,6 +270,9 @@ tp_button_bottom_handle_event(struct tp_dispatch *tp, tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, event); break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: case BUTTON_EVENT_IN_AREA: tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_TO_AREA, event); break; @@ -236,6 +298,9 @@ tp_button_bottom_new_handle_event(struct tp_dispatch *tp, tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, event); break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: case BUTTON_EVENT_IN_AREA: tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); break; @@ -268,6 +333,9 @@ tp_button_bottom_to_area_handle_event(struct tp_dispatch *tp, tp_button_set_state(tp, t, BUTTON_STATE_BOTTOM_NEW, event); break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: case BUTTON_EVENT_IN_AREA: break; case BUTTON_EVENT_UP: @@ -282,6 +350,125 @@ tp_button_bottom_to_area_handle_event(struct tp_dispatch *tp, } } +static void +tp_button_top_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_TOP_TO_IGNORE, event); + break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: + if (event != t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, + event); + break; + case BUTTON_EVENT_IN_AREA: + tp_button_set_state(tp, t, BUTTON_STATE_TOP_TO_IGNORE, 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_top_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: + tp_button_set_state(tp, t, BUTTON_STATE_AREA, event); + break; + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: + if (event != t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_TOP_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_TOP, event); + break; + case BUTTON_EVENT_RELEASE: + break; + case BUTTON_EVENT_TIMEOUT: + tp_button_set_state(tp, t, BUTTON_STATE_TOP, event); + break; + } +} + +static void +tp_button_top_to_ignore_handle_event(struct tp_dispatch *tp, + struct tp_touch *t, + enum button_event event) +{ + switch(event) { + case BUTTON_EVENT_IN_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_L: + if (event == t->button.curr) + tp_button_set_state(tp, t, BUTTON_STATE_TOP, + event); + else + tp_button_set_state(tp, t, BUTTON_STATE_TOP_NEW, + event); + break; + 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: + break; + case BUTTON_EVENT_TIMEOUT: + tp_button_set_state(tp, t, BUTTON_STATE_IGNORE, event); + break; + } +} + +static void +tp_button_ignore_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_TOP_R: + case BUTTON_EVENT_IN_TOP_M: + case BUTTON_EVENT_IN_TOP_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_handle_event(struct tp_dispatch *tp, struct tp_touch *t, @@ -306,6 +493,18 @@ tp_button_handle_event(struct tp_dispatch *tp, case BUTTON_STATE_BOTTOM_TO_AREA: tp_button_bottom_to_area_handle_event(tp, t, event); break; + case BUTTON_STATE_TOP: + tp_button_top_handle_event(tp, t, event); + break; + case BUTTON_STATE_TOP_NEW: + tp_button_top_new_handle_event(tp, t, event); + break; + case BUTTON_STATE_TOP_TO_IGNORE: + tp_button_top_to_ignore_handle_event(tp, t, event); + break; + case BUTTON_STATE_IGNORE: + tp_button_ignore_handle_event(tp, t, event); + break; } if (current != t->button.state) @@ -331,6 +530,12 @@ tp_button_handle_state(struct tp_dispatch *tp, uint64_t time) tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_R, time); else if (is_inside_bottom_left_area(tp, t)) tp_button_handle_event(tp, t, BUTTON_EVENT_IN_BOTTOM_L, time); + else if (is_inside_top_right_area(tp, t)) + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_R, time); + else if (is_inside_top_middle_area(tp, t)) + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_M, time); + else if (is_inside_top_left_area(tp, t)) + tp_button_handle_event(tp, t, BUTTON_EVENT_IN_TOP_L, time); else tp_button_handle_event(tp, t, BUTTON_EVENT_IN_AREA, time); } @@ -412,6 +617,8 @@ tp_init_buttons(struct tp_dispatch *tp, tp->buttons.is_clickpad = libevdev_has_property(device->evdev, INPUT_PROP_BUTTONPAD); + tp->buttons.has_topbuttons = libevdev_has_property(device->evdev, + INPUT_PROP_TOPBUTTONPAD); if (libevdev_has_event_code(device->evdev, EV_KEY, BTN_MIDDLE) || libevdev_has_event_code(device->evdev, EV_KEY, BTN_RIGHT)) { @@ -434,8 +641,16 @@ tp_init_buttons(struct tp_dispatch *tp, if (tp->buttons.is_clickpad && !tp->buttons.use_clickfinger) { tp->buttons.bottom_area.top_edge = height * .8 + device->abs.min_y; tp->buttons.bottom_area.rightbutton_left_edge = width/2 + device->abs.min_x; - tp->buttons.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); + if (tp->buttons.has_topbuttons) { + tp->buttons.top_area.bottom_edge = height * .08 + device->abs.min_y; + tp->buttons.top_area.rightbutton_left_edge = width * .58 + device->abs.min_x; + tp->buttons.top_area.leftbutton_right_edge = width * .42 + device->abs.min_x; + } else { + tp->buttons.top_area.bottom_edge = INT_MIN; + } + + tp->buttons.timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (tp->buttons.timer_fd == -1) return -1; @@ -448,6 +663,7 @@ tp_init_buttons(struct tp_dispatch *tp, return -1; } else { tp->buttons.bottom_area.top_edge = INT_MAX; + tp->buttons.top_area.bottom_edge = INT_MIN; } return 0; @@ -540,7 +756,7 @@ 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 }; + enum { AREA = 0x01, LEFT = 0x02, MIDDLE = 0x04, RIGHT = 0x08 }; current = tp->buttons.state; old = tp->buttons.old_state; @@ -558,9 +774,14 @@ tp_post_softbutton_buttons(struct tp_dispatch *tp, uint64_t time) button |= AREA; break; case BUTTON_EVENT_IN_BOTTOM_L: + case BUTTON_EVENT_IN_TOP_L: button |= LEFT; break; + case BUTTON_EVENT_IN_TOP_M: + button |= MIDDLE; + break; case BUTTON_EVENT_IN_BOTTOM_R: + case BUTTON_EVENT_IN_TOP_R: button |= RIGHT; break; default: @@ -568,25 +789,19 @@ tp_post_softbutton_buttons(struct tp_dispatch *tp, uint64_t time) } } - switch (button) { - case 0: + if (button == 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; } + if ((button & MIDDLE) || ((button & LEFT) && (button & RIGHT))) + button = BTN_MIDDLE; + else if (button & RIGHT) + button = BTN_RIGHT; + else + button = BTN_LEFT; + tp->buttons.active = button; state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; } else { diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index bcb84dc4..4d20d0d4 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -49,6 +49,9 @@ enum touch_state { enum button_event { BUTTON_EVENT_IN_BOTTOM_R = 30, BUTTON_EVENT_IN_BOTTOM_L, + BUTTON_EVENT_IN_TOP_R, + BUTTON_EVENT_IN_TOP_M, + BUTTON_EVENT_IN_TOP_L, BUTTON_EVENT_IN_AREA, BUTTON_EVENT_UP, BUTTON_EVENT_PRESS, @@ -62,6 +65,10 @@ enum button_state { BUTTON_STATE_BOTTOM, BUTTON_STATE_BOTTOM_NEW, BUTTON_STATE_BOTTOM_TO_AREA, + BUTTON_STATE_TOP, + BUTTON_STATE_TOP_NEW, + BUTTON_STATE_TOP_TO_IGNORE, + BUTTON_STATE_IGNORE, }; enum scroll_state { @@ -158,6 +165,7 @@ struct tp_dispatch { struct { bool is_clickpad; /* true for clickpads */ + bool has_topbuttons; bool use_clickfinger; /* number of fingers decides button number */ bool click_pending; uint32_t state; @@ -165,16 +173,21 @@ struct tp_dispatch { 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. + /* Only used for clickpads. The software button areas are + * always 2 horizontal stripes across the touchpad. + * The buttons are split according to the edge settings. */ struct { int32_t top_edge; int32_t rightbutton_left_edge; } bottom_area; + struct { + int32_t bottom_edge; + int32_t rightbutton_left_edge; + int32_t leftbutton_right_edge; + } top_area; + unsigned int timeout; /* current timeout in ms */ int timer_fd;