[PATCH 2/2] touchpad: Add edge-scrolling support
Hans de Goede
hdegoede at redhat.com
Fri Nov 7 05:25:06 PST 2014
Add edge-scrolling support for non multi-touch touchpads as well as for
users who prefer edge-scrolling (as long as they don't have a clickpad).
Note the percentage to use of the width / height as scroll-edge differs from
one manufacturer to the next, the various per model percentages were taken
from xf86-input-synaptics.
Signed-off-by: Hans de Goede <hdegoede at redhat.com>
---
src/Makefile.am | 1 +
src/evdev-mt-touchpad-edge-scroll.c | 366 ++++++++++++++++++++++++++++++++++++
src/evdev-mt-touchpad.c | 59 +++++-
src/evdev-mt-touchpad.h | 46 +++++
4 files changed, 463 insertions(+), 9 deletions(-)
create mode 100644 src/evdev-mt-touchpad-edge-scroll.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 5cc52a6..027e08c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,6 +15,7 @@ libinput_la_SOURCES = \
evdev-mt-touchpad.h \
evdev-mt-touchpad-tap.c \
evdev-mt-touchpad-buttons.c \
+ evdev-mt-touchpad-edge-scroll.c \
filter.c \
filter.h \
filter-private.h \
diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c
new file mode 100644
index 0000000..2968db4
--- /dev/null
+++ b/src/evdev-mt-touchpad-edge-scroll.c
@@ -0,0 +1,366 @@
+/*
+ * 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 <errno.h>
+#include <limits.h>
+#include <math.h>
+#include <string.h>
+#include <unistd.h>
+#include "linux/input.h"
+
+#include "evdev-mt-touchpad.h"
+
+#define DEFAULT_SCROLL_LOCK_TIMEOUT 300 /* ms */
+/* Use a reasonably large threshold until locked into scrolling mode, to
+ avoid accidentally locking in scrolling mode when trying to use the entire
+ touchpad to move the pointer. The user can wait for the timeout to trigger
+ to do a small scroll. */
+#define DEFAULT_SCROLL_THRESHOLD 10.0
+
+enum scroll_event {
+ SCROLL_EVENT_TOUCH,
+ SCROLL_EVENT_MOTION,
+ SCROLL_EVENT_RELEASE,
+ SCROLL_EVENT_TIMEOUT,
+};
+
+static uint32_t
+tp_touch_get_edge(struct tp_dispatch *tp, struct tp_touch *touch)
+{
+ uint32_t edge = EDGE_NONE;
+
+ if (tp->scroll.mode != LIBINPUT_CONFIG_SCROLL_EDGE)
+ return 0;
+
+ if (touch->x > tp->scroll.right_edge)
+ edge |= EDGE_RIGHT;
+
+ if (touch->y > tp->scroll.bottom_edge)
+ edge |= EDGE_BOTTOM;
+
+ return edge;
+}
+
+static void
+tp_edge_scroll_set_state(struct tp_dispatch *tp, struct tp_touch *t,
+ enum tp_edge_scroll_touch_state state)
+{
+ libinput_timer_cancel(&t->scroll.timer);
+
+ t->scroll.state = state;
+
+ switch (state) {
+ case EDGE_SCROLL_TOUCH_STATE_NONE:
+ t->scroll.edge = EDGE_NONE;
+ t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
+ libinput_timer_set(&t->scroll.timer,
+ t->millis + DEFAULT_SCROLL_LOCK_TIMEOUT);
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_EDGE:
+ t->scroll.threshold = 0.01; /* Do not allow 0.0 events */
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_AREA:
+ t->scroll.edge = EDGE_NONE;
+ tp_set_pointer(tp, t);
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_none(struct tp_dispatch *tp, struct tp_touch *t,
+ enum scroll_event event)
+{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+
+ switch (event) {
+ case SCROLL_EVENT_TOUCH:
+ t->scroll.edge = tp_touch_get_edge(tp, t);
+ if (t->scroll.edge) {
+ tp_edge_scroll_set_state(tp, t,
+ EDGE_SCROLL_TOUCH_STATE_EDGE_NEW);
+ } else {
+ tp_edge_scroll_set_state(tp, t,
+ EDGE_SCROLL_TOUCH_STATE_AREA);
+ }
+ break;
+ case SCROLL_EVENT_MOTION:
+ case SCROLL_EVENT_RELEASE:
+ case SCROLL_EVENT_TIMEOUT:
+ log_bug_libinput(libinput,
+ "unexpect scroll event in none state\n");
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_edge_new(struct tp_dispatch *tp, struct tp_touch *t,
+ enum scroll_event event)
+{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+
+ switch (event) {
+ case SCROLL_EVENT_TOUCH:
+ log_bug_libinput(libinput,
+ "unexpect scroll event in edge new state\n");
+ break;
+ case SCROLL_EVENT_MOTION:
+ t->scroll.edge &= tp_touch_get_edge(tp, t);
+ if (!t->scroll.edge)
+ tp_edge_scroll_set_state(tp, t,
+ EDGE_SCROLL_TOUCH_STATE_AREA);
+ break;
+ case SCROLL_EVENT_RELEASE:
+ tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+ break;
+ case SCROLL_EVENT_TIMEOUT:
+ tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_EDGE);
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_edge(struct tp_dispatch *tp, struct tp_touch *t,
+ enum scroll_event event)
+{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+
+ switch (event) {
+ case SCROLL_EVENT_TOUCH:
+ case SCROLL_EVENT_TIMEOUT:
+ log_bug_libinput(libinput,
+ "unexpect scroll event in edge state\n");
+ break;
+ case SCROLL_EVENT_MOTION:
+ /* If started at the bottom right, decide in which dir to scroll */
+ if (t->scroll.edge == (EDGE_RIGHT | EDGE_BOTTOM)) {
+ t->scroll.edge &= tp_touch_get_edge(tp, t);
+ if (!t->scroll.edge)
+ tp_edge_scroll_set_state(tp, t,
+ EDGE_SCROLL_TOUCH_STATE_AREA);
+ }
+ break;
+ case SCROLL_EVENT_RELEASE:
+ tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_area(struct tp_dispatch *tp, struct tp_touch *t,
+ enum scroll_event event)
+{
+ struct libinput *libinput = tp->device->base.seat->libinput;
+
+ switch (event) {
+ case SCROLL_EVENT_TOUCH:
+ case SCROLL_EVENT_TIMEOUT:
+ log_bug_libinput(libinput,
+ "unexpect scroll event in area state\n");
+ break;
+ case SCROLL_EVENT_MOTION:
+ break;
+ case SCROLL_EVENT_RELEASE:
+ tp_edge_scroll_set_state(tp, t, EDGE_SCROLL_TOUCH_STATE_NONE);
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_event(struct tp_dispatch *tp, struct tp_touch *t,
+ enum scroll_event event)
+{
+ switch (t->scroll.state) {
+ case EDGE_SCROLL_TOUCH_STATE_NONE:
+ tp_edge_scroll_handle_none(tp, t, event);
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_EDGE_NEW:
+ tp_edge_scroll_handle_edge_new(tp, t, event);
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_EDGE:
+ tp_edge_scroll_handle_edge(tp, t, event);
+ break;
+ case EDGE_SCROLL_TOUCH_STATE_AREA:
+ tp_edge_scroll_handle_area(tp, t, event);
+ break;
+ }
+}
+
+static void
+tp_edge_scroll_handle_timeout(uint64_t now, void *data)
+{
+ struct tp_touch *t = data;
+
+ tp_edge_scroll_handle_event(t->tp, t, SCROLL_EVENT_TIMEOUT);
+}
+
+int
+tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device)
+{
+ struct tp_touch *t;
+ int width, height;
+ int edge_width, edge_height;
+
+ width = abs(device->abs.absinfo_x->maximum -
+ device->abs.absinfo_x->minimum);
+ height = abs(device->abs.absinfo_y->maximum -
+ device->abs.absinfo_y->minimum);
+
+ switch (tp->model) {
+ case MODEL_SYNAPTICS:
+ edge_width = width * .07;
+ edge_height = height * .07;
+ break;
+ case MODEL_ALPS:
+ edge_width = width * .15;
+ edge_height = height * .15;
+ break;
+ case MODEL_APPLETOUCH:
+ case MODEL_UNIBODY_MACBOOK:
+ edge_width = width * .085;
+ edge_height = height * .085;
+ break;
+ default:
+ edge_width = width * .04;
+ edge_height = height * .054;
+ }
+
+ tp->scroll.right_edge = device->abs.absinfo_x->maximum - edge_width;
+ tp->scroll.bottom_edge = device->abs.absinfo_y->maximum - edge_height;
+
+ tp_for_each_touch(tp, t) {
+ t->scroll.last_axis = -1;
+ t->scroll.threshold = DEFAULT_SCROLL_THRESHOLD;
+ libinput_timer_init(&t->scroll.timer,
+ device->base.seat->libinput,
+ tp_edge_scroll_handle_timeout, t);
+ }
+
+ return 0;
+}
+
+void
+tp_destroy_edge_scroll(struct tp_dispatch *tp)
+{
+ struct tp_touch *t;
+
+ tp_for_each_touch(tp, t)
+ libinput_timer_cancel(&t->scroll.timer);
+}
+
+void
+tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time)
+{
+ struct tp_touch *t;
+
+ tp_for_each_touch(tp, t) {
+ if (!t->dirty)
+ continue;
+
+ switch (t->state) {
+ case TOUCH_NONE:
+ break;
+ case TOUCH_BEGIN:
+ tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_TOUCH);
+ break;
+ case TOUCH_UPDATE:
+ tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_MOTION);
+ break;
+ case TOUCH_END:
+ tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_RELEASE);
+ break;
+ }
+ }
+}
+
+int
+tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
+{
+ struct libinput_device *device = &tp->device->base;
+ struct tp_touch *t;
+ enum libinput_pointer_axis axis;
+ double dx, dy, *delta;
+
+ tp_for_each_touch(tp, t) {
+ if (!t->dirty)
+ continue;
+
+ switch (t->scroll.edge) {
+ case EDGE_NONE:
+ if (t->scroll.last_axis != -1) {
+ /* Send stop scroll event */
+ pointer_notify_axis(device, time,
+ t->scroll.last_axis, 0.0);
+ t->scroll.last_axis = -1;
+ }
+ continue;
+ case EDGE_RIGHT:
+ axis = LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
+ delta = &dy;
+ break;
+ case EDGE_BOTTOM:
+ axis = LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL;
+ delta = &dx;
+ break;
+ default: /* EDGE_RIGHT | EDGE_BOTTOM */
+ continue; /* Don't know direction yet, skip */
+ }
+
+ tp_get_delta(t, &dx, &dy);
+ tp_filter_motion(tp, &dx, &dy, time);
+
+ if (fabs(*delta) < t->scroll.threshold)
+ continue;
+
+ pointer_notify_axis(device, time, axis, *delta);
+ t->scroll.last_axis = axis;
+
+ if (t->scroll.state == EDGE_SCROLL_TOUCH_STATE_EDGE_NEW) {
+ tp_edge_scroll_set_state(tp, t,
+ EDGE_SCROLL_TOUCH_STATE_EDGE);
+ }
+ }
+
+ return 0; /* Edge touches are suppressed by edge_scroll_touch_active */
+}
+
+void
+tp_edge_scroll_stop(struct tp_dispatch *tp, uint64_t time)
+{
+ struct libinput_device *device = &tp->device->base;
+ struct tp_touch *t;
+
+ tp_for_each_touch(tp, t) {
+ if (t->scroll.last_axis != -1) {
+ pointer_notify_axis(device, time,
+ t->scroll.last_axis, 0.0);
+ t->scroll.last_axis = -1;
+ }
+ }
+}
+
+int
+tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+{
+ return t->scroll.state == EDGE_SCROLL_TOUCH_STATE_AREA;
+}
diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c
index d2ee6ea..fb78fcd 100644
--- a/src/evdev-mt-touchpad.c
+++ b/src/evdev-mt-touchpad.c
@@ -56,7 +56,7 @@ tp_motion_history_offset(struct tp_touch *t, int offset)
return &t->history.samples[offset_index];
}
-static void
+void
tp_filter_motion(struct tp_dispatch *tp,
double *dx, double *dy, uint64_t time)
{
@@ -339,7 +339,9 @@ tp_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
{
return (t->state == TOUCH_BEGIN || t->state == TOUCH_UPDATE) &&
!t->palm.is_palm &&
- !t->pinned.is_pinned && tp_button_touch_active(tp, t);
+ !t->pinned.is_pinned &&
+ tp_button_touch_active(tp, t) &&
+ tp_edge_scroll_touch_active(tp, t);
}
void
@@ -431,6 +433,7 @@ tp_process_state(struct tp_dispatch *tp, uint64_t time)
}
tp_button_handle_state(tp, time);
+ tp_edge_scroll_handle_state(tp, time);
/*
* We have a physical button down event on a clickpad. To avoid
@@ -503,15 +506,12 @@ tp_post_twofinger_scroll(struct tp_dispatch *tp, uint64_t time)
}
static int
-tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
+tp_2fg_scroll_post_events(struct tp_dispatch *tp, uint64_t time)
{
struct tp_touch *t;
int nfingers_down = 0;
- if (tp->scroll.mode != LIBINPUT_CONFIG_SCROLL_2FG)
- return 0;
-
- /* No scrolling during tap-n-drag */
+ /* No 2fg scrolling during tap-n-drag */
if (tp_tap_dragging(tp))
return 0;
@@ -530,6 +530,22 @@ tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
return 1;
}
+static int
+tp_post_scroll_events(struct tp_dispatch *tp, uint64_t time)
+{
+ switch (tp->scroll.mode) {
+ case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
+ return 0;
+ case LIBINPUT_CONFIG_SCROLL_2FG:
+ return tp_2fg_scroll_post_events(tp, time);
+ case LIBINPUT_CONFIG_SCROLL_EDGE:
+ return tp_edge_scroll_post_events(tp, time);
+ case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
+ return 0; /* Not supported */
+ }
+ return 0; /* Never reached */
+}
+
static void
tp_post_events(struct tp_dispatch *tp, uint64_t time)
{
@@ -626,6 +642,7 @@ tp_destroy(struct evdev_dispatch *dispatch)
tp_destroy_tap(tp);
tp_destroy_buttons(tp);
tp_destroy_sendevents(tp);
+ tp_destroy_edge_scroll(tp);
free(tp->touches);
free(tp);
@@ -958,6 +975,9 @@ tp_scroll_config_scroll_mode_get_modes(struct libinput_device *device)
if (tp->ntouches >= 2)
modes |= LIBINPUT_CONFIG_SCROLL_2FG;
+ if (!tp->buttons.is_clickpad)
+ modes |= LIBINPUT_CONFIG_SCROLL_EDGE;
+
return modes;
}
@@ -971,7 +991,18 @@ tp_scroll_config_scroll_mode_set_mode(struct libinput_device *device,
if (mode == tp->scroll.mode)
return LIBINPUT_CONFIG_STATUS_SUCCESS;
- evdev_stop_scroll(evdev, libinput_now(device->seat->libinput));
+ switch (tp->scroll.mode) {
+ case LIBINPUT_CONFIG_SCROLL_NO_SCROLL:
+ case LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN:
+ break; /* Not supported */
+ case LIBINPUT_CONFIG_SCROLL_2FG:
+ evdev_stop_scroll(evdev, libinput_now(device->seat->libinput));
+ break;
+ case LIBINPUT_CONFIG_SCROLL_EDGE:
+ tp_edge_scroll_stop(tp, libinput_now(device->seat->libinput));
+ break;
+ }
+
tp->scroll.mode = mode;
return LIBINPUT_CONFIG_STATUS_SUCCESS;
@@ -989,7 +1020,13 @@ tp_scroll_config_scroll_mode_get_mode(struct libinput_device *device)
static enum libinput_config_scroll_mode
tp_scroll_config_scroll_mode_get_default_mode(struct libinput_device *device)
{
- return LIBINPUT_CONFIG_SCROLL_2FG;
+ struct evdev_device *evdev = (struct evdev_device*)device;
+ struct tp_dispatch *tp = (struct tp_dispatch*)evdev->dispatch;
+
+ if (tp->ntouches >= 2)
+ return LIBINPUT_CONFIG_SCROLL_2FG;
+ else
+ return LIBINPUT_CONFIG_SCROLL_EDGE;
}
static int
@@ -1096,6 +1133,9 @@ tp_init(struct tp_dispatch *tp,
if (tp_init_scroll(tp) != 0)
return -1;
+ if (tp_edge_scroll_init(tp, device) != 0)
+ return -1;
+
device->seat_caps |= EVDEV_DEVICE_POINTER;
return 0;
@@ -1239,6 +1279,7 @@ evdev_mt_touchpad_create(struct evdev_device *device)
tp->model = tp_get_model(device);
+ device->dispatch = &tp->base;
if (tp_init(tp, device) != 0) {
tp_destroy(&tp->base);
return NULL;
diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h
index d012458..d1eb8a5 100644
--- a/src/evdev-mt-touchpad.h
+++ b/src/evdev-mt-touchpad.h
@@ -103,6 +103,20 @@ enum tp_tap_touch_state {
TAP_TOUCH_STATE_DEAD, /**< exceeded motion/timeout */
};
+/* For edge scrolling, so we only care about right and bottom */
+enum tp_edge {
+ EDGE_NONE = 0,
+ EDGE_RIGHT = (1 << 0),
+ EDGE_BOTTOM = (1 << 1),
+};
+
+enum tp_edge_scroll_touch_state {
+ EDGE_SCROLL_TOUCH_STATE_NONE,
+ EDGE_SCROLL_TOUCH_STATE_EDGE_NEW,
+ EDGE_SCROLL_TOUCH_STATE_EDGE,
+ EDGE_SCROLL_TOUCH_STATE_AREA,
+};
+
struct tp_motion {
int32_t x;
int32_t y;
@@ -151,6 +165,14 @@ struct tp_touch {
} tap;
struct {
+ enum tp_edge_scroll_touch_state state;
+ uint32_t edge;
+ int last_axis;
+ double threshold;
+ struct libinput_timer timer;
+ } scroll;
+
+ struct {
bool is_palm;
int32_t x, y; /* first coordinates if is_palm == true */
uint32_t time; /* first timestamp if is_palm == true */
@@ -216,6 +238,8 @@ struct tp_dispatch {
struct libinput_device_config_scroll_mode config_mode;
bool natural_scrolling_enabled;
enum libinput_config_scroll_mode mode;
+ int32_t right_edge;
+ int32_t bottom_edge;
} scroll;
enum touchpad_event queued;
@@ -252,6 +276,10 @@ tp_get_delta(struct tp_touch *t, double *dx, double *dy);
void
tp_set_pointer(struct tp_dispatch *tp, struct tp_touch *t);
+void
+tp_filter_motion(struct tp_dispatch *tp,
+ double *dx, double *dy, uint64_t time);
+
int
tp_tap_handle_state(struct tp_dispatch *tp, uint64_t time);
@@ -306,4 +334,22 @@ tp_tap_resume(struct tp_dispatch *tp, uint64_t time);
bool
tp_tap_dragging(struct tp_dispatch *tp);
+int
+tp_edge_scroll_init(struct tp_dispatch *tp, struct evdev_device *device);
+
+void
+tp_destroy_edge_scroll(struct tp_dispatch *tp);
+
+void
+tp_edge_scroll_handle_state(struct tp_dispatch *tp, uint64_t time);
+
+int
+tp_edge_scroll_post_events(struct tp_dispatch *tp, uint64_t time);
+
+void
+tp_edge_scroll_stop(struct tp_dispatch *tp, uint64_t time);
+
+int
+tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t);
+
#endif
--
2.1.0
More information about the wayland-devel
mailing list