From e192ecc6e934fd5f77dbc9d8a21746947e8fa61d Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Fri, 28 Mar 2014 09:44:11 +1000 Subject: [PATCH] touchpad: Add clickpad-style software buttons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Almost all non Apple touchpads have visible markings for software button areas, so limit clickfinger behavior to Apple clickpads, and implement software button areas for others. This is a slightly fancier implementation than the simplest model and ported over from libtouchpad. It implements a state machine for the software buttons with left and right buttons currently implemented. Buttons are oriented left-to-right, in a horizontal bar. No random button placement allowed. In general, the procedure is: - if a finger sets down in the left button area, a click is a left click - if a finger sets down in the right button area, a click is a right click - if a finger leaves the button area, a click is a left click - if a finger starts outside the button area, a click is a left click Two timeouts are used to handle buttons more smoothly: - if a finger sets down in a button area but "immediately" moves over to a different area, that area takes effect on a click. - if a finger leaves a button area and "immediately" clicks or moves back into the area, the button still takes effect on a click. - if a finger changes between areas and stays there for a timeout, that area takes effect on a click. Note the button area states are named BOTTOM_foo to make it easier to later add support for a top button area such as can be found on the Thinkpad [2-5]40 series. Co-authored-by: Hans de Goede Signed-off-by: Peter Hutterer Signed-off-by: Hans de Goede Reviewed-by: Jonas Ã…dahl Reviewed-by: Hans de Goede Reviewed-by: Peter Hutterer --- doc/Makefile.am | 2 +- doc/touchpad-softbutton-state-machine.svg | 173 ++++++++ src/evdev-mt-touchpad-buttons.c | 455 +++++++++++++++++++++- src/evdev-mt-touchpad.c | 15 + src/evdev-mt-touchpad.h | 49 +++ src/libinput.h | 40 ++ test/touchpad.c | 12 +- 7 files changed, 739 insertions(+), 7 deletions(-) create mode 100644 doc/touchpad-softbutton-state-machine.svg diff --git a/doc/Makefile.am b/doc/Makefile.am index 75fa98a4..a33638da 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,4 @@ -EXTRA_DIST = touchpad-tap-state-machine.svg +EXTRA_DIST = touchpad-tap-state-machine.svg touchpad-softbutton-state-machine.svg if HAVE_DOXYGEN diff --git a/doc/touchpad-softbutton-state-machine.svg b/doc/touchpad-softbutton-state-machine.svg new file mode 100644 index 00000000..1838e354 --- /dev/null +++ b/doc/touchpad-softbutton-state-machine.svg @@ -0,0 +1,173 @@ + + + + + + + + +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 + + + + + + + + +ANY + + + + + + + + + + + + +finger in + +bottom + +button == curr + + + + + + + + + + + diff --git a/src/evdev-mt-touchpad-buttons.c b/src/evdev-mt-touchpad-buttons.c index 21241de7..1ddd8df9 100644 --- a/src/evdev-mt-touchpad-buttons.c +++ b/src/evdev-mt-touchpad-buttons.c @@ -20,11 +20,345 @@ * 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, uint32_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; + 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, + uint32_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, uint32_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 int +tp_button_handle_timeout(struct tp_dispatch *tp, uint32_t now) +{ + struct tp_touch *t; + uint32_t min_timeout = INT_MAX; + + 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); + } + if (t->button.timeout != 0) + min_timeout = min(t->button.timeout, min_timeout); + } + + return min_timeout == INT_MAX ? 0 : min_timeout; +} int tp_process_button(struct tp_dispatch *tp, @@ -43,6 +377,28 @@ tp_process_button(struct tp_dispatch *tp, return 0; } +static void +tp_button_timeout_handler(void *data) +{ + struct tp_dispatch *tp = data; + uint64_t expires; + int len; + struct timespec ts; + uint32_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 * 1000 + ts.tv_nsec / 1000000; + + tp_button_handle_timeout(tp, now); +} + int tp_init_buttons(struct tp_dispatch *tp, struct evdev_device *device) @@ -60,9 +416,48 @@ tp_init_buttons(struct tp_dispatch *tp, tp->buttons.motion_dist = diagonal * DEFAULT_BUTTON_MOTION_THRESHOLD; + if (libevdev_get_id_vendor(device->evdev) == 0x5ac) /* Apple */ + tp->buttons.use_clickfinger = true; + + tp->buttons.use_softbuttons = !tp->buttons.use_clickfinger && + !tp->buttons.has_buttons; + + if (tp->buttons.use_softbuttons) { + 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, uint32_t time) { @@ -131,10 +526,63 @@ tp_post_physical_buttons(struct tp_dispatch *tp, uint32_t time) return 0; } +static int +tp_post_softbutton_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; + + if (tp->nfingers_down == 0 || tp->nfingers_down > 2) + return 0; + + if (current) { + struct tp_touch *t; + button = 0; + + tp_for_each_touch(tp, t) { + if (t->button.curr == BUTTON_EVENT_IN_BOTTOM_R) + button |= 0x2; + else if (t->button.curr == BUTTON_EVENT_IN_BOTTOM_L) + button |= 0x1; + } + + switch (button) { + case 0: /* only in area */ + case 1: /* only left area */ + button = BTN_LEFT; + break; + case 2: /* only right area */ + button = BTN_RIGHT; + break; + case 3: /* left + right area */ + button = BTN_MIDDLE; + break; + } + + tp->buttons.active = button; + state = LIBINPUT_POINTER_BUTTON_STATE_PRESSED; + } else { + state = LIBINPUT_POINTER_BUTTON_STATE_RELEASED; + button = tp->buttons.active; + } + + pointer_notify_button(&tp->device->base, + time, + button, + state); + return 1; +} + int tp_post_button_events(struct tp_dispatch *tp, uint32_t time) { - int rc; + int rc = 0; if ((tp->queued & (TOUCHPAD_EVENT_BUTTON_PRESS|TOUCHPAD_EVENT_BUTTON_RELEASE)) == 0) @@ -142,8 +590,11 @@ tp_post_button_events(struct tp_dispatch *tp, uint32_t time) if (tp->buttons.has_buttons) rc = tp_post_physical_buttons(tp, time); - else + else if (tp->buttons.use_clickfinger) rc = tp_post_clickfinger_buttons(tp, time); + else if (tp->buttons.use_softbuttons) + rc = tp_post_softbutton_buttons(tp, time); + return rc; } diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index c86b0578..7f73f6ee 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -399,6 +399,8 @@ tp_process_state(struct tp_dispatch *tp, uint32_t time) tp_unpin_finger(tp, t); } + 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. @@ -596,6 +598,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); @@ -608,10 +611,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); @@ -649,6 +660,9 @@ tp_init_slots(struct tp_dispatch *tp, if (!tp->touches) return -1; + for (i = 0; i < tp->ntouches; i++) + tp_init_touch(tp, &tp->touches[i]); + return 0; } @@ -690,6 +704,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; diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 85cf7e54..8d8dd840 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 @@ -101,6 +119,14 @@ struct tp_touch { 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; + uint32_t timeout; + } button; }; struct tp_dispatch { @@ -129,10 +155,27 @@ struct tp_dispatch { struct { bool has_buttons; /* true for physical LMR buttons */ + bool use_clickfinger; /* number of fingers decides button number */ + bool use_softbuttons; /* use software-button area */ 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 if has_buttons is false. 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 { @@ -172,6 +215,9 @@ 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, @@ -180,4 +226,7 @@ tp_process_button(struct tp_dispatch *tp, int tp_post_button_events(struct tp_dispatch *tp, uint32_t time); +int +tp_button_handle_state(struct tp_dispatch *tp, uint32_t time); + #endif 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/touchpad.c b/test/touchpad.c index f4d78391..bbae6cd8 100644 --- a/test/touchpad.c +++ b/test/touchpad.c @@ -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);