[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