summaryrefslogtreecommitdiff
path: root/src/evdev-mt-touchpad-edge-scroll.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/evdev-mt-touchpad-edge-scroll.c')
-rw-r--r--src/evdev-mt-touchpad-edge-scroll.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/src/evdev-mt-touchpad-edge-scroll.c b/src/evdev-mt-touchpad-edge-scroll.c
new file mode 100644
index 00000000..1dca0eab
--- /dev/null
+++ b/src/evdev-mt-touchpad-edge-scroll.c
@@ -0,0 +1,374 @@
+/*
+ * 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. */
+/* In mm for touchpads with valid resolution, see tp_init_accel() */
+#define DEFAULT_SCROLL_THRESHOLD 10.0
+
+enum scroll_event {
+ SCROLL_EVENT_TOUCH,
+ SCROLL_EVENT_MOTION,
+ SCROLL_EVENT_RELEASE,
+ SCROLL_EVENT_TIMEOUT,
+ SCROLL_EVENT_POSTED,
+};
+
+static uint32_t
+tp_touch_get_edge(struct tp_dispatch *tp, struct tp_touch *touch)
+{
+ uint32_t edge = EDGE_NONE;
+
+ if (tp->scroll.method != LIBINPUT_CONFIG_SCROLL_EDGE)
+ return EDGE_NONE;
+
+ 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:
+ t->scroll.edge = tp_touch_get_edge(tp, t);
+ 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:
+ if (tp_touch_get_edge(tp, t)) {
+ 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:
+ case SCROLL_EVENT_POSTED:
+ 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:
+ case SCROLL_EVENT_POSTED:
+ 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;
+ case SCROLL_EVENT_POSTED:
+ 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:
+ case SCROLL_EVENT_POSTED:
+ 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 = device->abs.absinfo_x->maximum - device->abs.absinfo_x->minimum;
+ height = device->abs.absinfo_y->maximum - device->abs.absinfo_y->minimum;
+
+ switch (tp->model) {
+ case MODEL_ALPS:
+ edge_width = width * .15;
+ edge_height = height * .15;
+ break;
+ case MODEL_APPLETOUCH: /* unibody are all clickpads, so N/A */
+ edge_width = width * .085;
+ edge_height = height * .085;
+ break;
+ default:
+ /* For elantech and synaptics, note for lenovo #40 series,
+ * e.g. the T440s min/max are the absolute edges, not the
+ * recommended ones as usual with synaptics. But these are
+ * clickpads, so N/A.
+ */
+ 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.direction = -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.direction != -1) {
+ /* Send stop scroll event */
+ pointer_notify_axis(device, time,
+ t->scroll.direction, 0.0);
+ t->scroll.direction = -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.direction = axis;
+
+ tp_edge_scroll_handle_event(tp, t, SCROLL_EVENT_POSTED);
+ }
+
+ return 0; /* Edge touches are suppressed by edge_scroll_touch_active */
+}
+
+void
+tp_edge_scroll_stop_events(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.direction != -1) {
+ pointer_notify_axis(device, time,
+ t->scroll.direction, 0.0);
+ t->scroll.direction = -1;
+ }
+ }
+}
+
+int
+tp_edge_scroll_touch_active(struct tp_dispatch *tp, struct tp_touch *t)
+{
+ return t->scroll.state == EDGE_SCROLL_TOUCH_STATE_AREA;
+}