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 @@
+
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);