diff --git a/meson.build b/meson.build index 8b536053..7bd33405 100644 --- a/meson.build +++ b/meson.build @@ -436,6 +436,7 @@ src_libinput = src_libfilter + [ 'src/evdev-mt-touchpad-thumb.c', 'src/evdev-mt-touchpad-buttons.c', 'src/evdev-mt-touchpad-edge-scroll.c', + 'src/evdev-mt-touchpad-edge-motion.c', 'src/evdev-mt-touchpad-gestures.c', 'src/evdev-tablet.c', 'src/evdev-tablet-pad.c', diff --git a/src/evdev-mt-touchpad-edge-motion.c b/src/evdev-mt-touchpad-edge-motion.c new file mode 100644 index 00000000..29ec128d --- /dev/null +++ b/src/evdev-mt-touchpad-edge-motion.c @@ -0,0 +1,222 @@ +/* + * Copyright © 2014-2015 Nobody, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * TOUCHPAD EDGE MOTION + * + * This module implements automatic cursor motion when performing tap-and-drag + * operations near the edges of a touchpad. When a user starts dragging content + * and reaches the edge of the touchpad, the system automatically continues + * moving the cursor in that direction to allow selection/dragging of content + * that extends beyond the physical touchpad boundaries. + */ + +#include "config.h" + +#include +#include + +#include "evdev-mt-touchpad.h" + +#define EDGE_MOTION_CONFIG_SPEED_MM_S 70.0 +#define EDGE_MOTION_CONFIG_MIN_INTERVAL_US 8000 +#define EDGE_MOTION_CONFIG_EDGE_THRESHOLD_MM 5.0 + +static void +calculate_motion_vector(uint32_t edge, double *dx, double *dy) +{ + *dx = 0.0; + *dy = 0.0; + + if (edge & EDGE_LEFT) + *dx = -1.0; + else if (edge & EDGE_RIGHT) + *dx = 1.0; + + if (edge & EDGE_TOP) + *dy = -1.0; + else if (edge & EDGE_BOTTOM) + *dy = 1.0; + + double mag = hypot(*dx, *dy); + if (mag > 0) { + *dx /= mag; + *dy /= mag; + } +} + +static struct device_float_coords +edge_motion_calculate_raw(struct tp_dispatch *tp, double dx, double dy, double dist_mm) +{ + struct device_float_coords raw = { + .x = dx * dist_mm * tp->accel.x_scale_coeff, + .y = dy * dist_mm * tp->accel.y_scale_coeff, + }; + + return raw; +} + +static void +inject_accumulated_motion(struct tp_dispatch *tp, usec_t time) +{ + if (usec_is_zero(tp->edge_motion.last_motion_time)) { + tp->edge_motion.last_motion_time = time; + return; + } + + usec_t time_since_last = usec_sub(time, tp->edge_motion.last_motion_time); + double dist_mm = + EDGE_MOTION_CONFIG_SPEED_MM_S * usec_to_seconds(time_since_last); + + if (dist_mm < 0.001) + return; + + struct device_float_coords raw = + edge_motion_calculate_raw(tp, + tp->edge_motion.motion_dx, + tp->edge_motion.motion_dy, + dist_mm); + + struct normalized_coords delta = + filter_dispatch(tp->device->pointer.filter, &raw, tp, time); + + pointer_notify_motion(&tp->device->base, time, &delta, &raw); + + tp->edge_motion.last_motion_time = time; + tp->edge_motion.continuous_motion_count++; +} + +static uint32_t +detect_touch_edge(const struct tp_dispatch *tp, const struct tp_touch *t) +{ + uint32_t edge = EDGE_NONE; + struct phys_coords mm = { EDGE_MOTION_CONFIG_EDGE_THRESHOLD_MM, + EDGE_MOTION_CONFIG_EDGE_THRESHOLD_MM }; + struct device_coords threshold = evdev_device_mm_to_units(tp->device, &mm); + + if (t->point.x < threshold.x) + edge |= EDGE_LEFT; + if (t->point.x > tp->device->abs.absinfo_x->maximum - threshold.x) + edge |= EDGE_RIGHT; + if (t->point.y < threshold.y) + edge |= EDGE_TOP; + if (t->point.y > tp->device->abs.absinfo_y->maximum - threshold.y) + edge |= EDGE_BOTTOM; + + return edge; +} + +static void +tp_edge_motion_handle_timeout(usec_t now, void *data) +{ + struct tp_dispatch *tp = data; + + if (tp->edge_motion.state != EDGE_MOTION_STATE_EDGE_MOTION) + return; + + inject_accumulated_motion(tp, now); + libinput_timer_set( + &tp->edge_motion.timer, + usec_add(now, usec_from_uint64_t(EDGE_MOTION_CONFIG_MIN_INTERVAL_US))); +} + +void +tp_init_edge_motion(struct tp_dispatch *tp) +{ + tp->edge_motion.state = EDGE_MOTION_STATE_IDLE; + tp->edge_motion.last_motion_time = usec_from_uint64_t(0); + tp->edge_motion.current_edge = EDGE_NONE; + tp->edge_motion.motion_dx = 0.0; + tp->edge_motion.motion_dy = 0.0; + tp->edge_motion.continuous_motion_count = 0; + + libinput_timer_init(&tp->edge_motion.timer, + tp_libinput_context(tp), + "edge drag motion", + tp_edge_motion_handle_timeout, + tp); +} + +void +tp_remove_edge_motion(struct tp_dispatch *tp) +{ + libinput_timer_cancel(&tp->edge_motion.timer); + libinput_timer_destroy(&tp->edge_motion.timer); +} + +int +tp_edge_motion_handle_drag_state(struct tp_dispatch *tp, usec_t time) +{ + bool drag_active = tp_tap_dragging(tp); + + uint32_t detected_edge = EDGE_NONE; + struct tp_touch *t; + + if (drag_active) { + tp_for_each_touch(tp, t) { + if (t->state != TOUCH_NONE && t->state != TOUCH_HOVERING) { + detected_edge = detect_touch_edge(tp, t); + break; + } + } + } + + enum tp_edge_motion_state next_state = EDGE_MOTION_STATE_IDLE; + if (drag_active) { + next_state = (detected_edge != EDGE_NONE) + ? EDGE_MOTION_STATE_EDGE_MOTION + : EDGE_MOTION_STATE_DRAG_ACTIVE; + } + + if (next_state != tp->edge_motion.state) { + tp->edge_motion.state = next_state; + tp->edge_motion.current_edge = detected_edge; + + if (tp->edge_motion.state != EDGE_MOTION_STATE_EDGE_MOTION) + tp->edge_motion.continuous_motion_count = 0; + + switch (tp->edge_motion.state) { + case EDGE_MOTION_STATE_IDLE: + case EDGE_MOTION_STATE_DRAG_ACTIVE: + libinput_timer_cancel(&tp->edge_motion.timer); + tp->edge_motion.current_edge = EDGE_NONE; + break; + + case EDGE_MOTION_STATE_EDGE_MOTION: + calculate_motion_vector(tp->edge_motion.current_edge, + &tp->edge_motion.motion_dx, + &tp->edge_motion.motion_dy); + tp->edge_motion.last_motion_time = time; + tp_edge_motion_handle_timeout(time, tp); + break; + } + } else if (tp->edge_motion.state == EDGE_MOTION_STATE_EDGE_MOTION && + detected_edge != tp->edge_motion.current_edge) { + tp->edge_motion.current_edge = detected_edge; + calculate_motion_vector(tp->edge_motion.current_edge, + &tp->edge_motion.motion_dx, + &tp->edge_motion.motion_dy); + } + + return (tp->edge_motion.state == EDGE_MOTION_STATE_EDGE_MOTION); +} diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 596ba944..5449ecd8 100644 --- a/src/evdev-mt-touchpad-tap.c +++ b/src/evdev-mt-touchpad-tap.c @@ -1292,6 +1292,9 @@ tp_tap_handle_state(struct tp_dispatch *tp, usec_t time) } } + /* Log 1-finger drag state changes */ + filter_motion |= tp_edge_motion_handle_drag_state(tp, time); + /** * In any state where motion exceeding the move threshold would * move to the next state, filter that motion until we actually diff --git a/src/evdev-mt-touchpad.c b/src/evdev-mt-touchpad.c index dafc0000..52b40e72 100644 --- a/src/evdev-mt-touchpad.c +++ b/src/evdev-mt-touchpad.c @@ -1960,6 +1960,7 @@ tp_interface_remove(struct evdev_dispatch *dispatch) tp_remove_buttons(tp); tp_remove_sendevents(tp); tp_remove_edge_scroll(tp); + tp_remove_edge_motion(tp); tp_remove_gesture(tp); } @@ -3799,6 +3800,7 @@ tp_init(struct tp_dispatch *tp, struct evdev_device *device) tp_init_palmdetect(tp, device); tp_init_sendevents(tp, device); tp_init_scroll(tp, device); + tp_init_edge_motion(tp); tp_init_gesture(tp); tp_init_thumb(tp); diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 7e85baf7..d86ee7a7 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -140,11 +140,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_motion_state { + EDGE_MOTION_STATE_IDLE, + EDGE_MOTION_STATE_DRAG_ACTIVE, + EDGE_MOTION_STATE_EDGE_MOTION, +}; + +/* For edge scrolling, so we only care about right and bottom. For edge motion, we + * require all four edges */ enum tp_edge { EDGE_NONE = 0, EDGE_RIGHT = bit(0), EDGE_BOTTOM = bit(1), + EDGE_LEFT = bit(2), + EDGE_TOP = bit(3), }; enum tp_edge_scroll_touch_state { @@ -421,6 +430,8 @@ struct tp_dispatch { enum libinput_config_scroll_method method; int32_t right_edge; /* in device coordinates */ int32_t bottom_edge; /* in device coordinates */ + int32_t left_edge; /* in device coordinates */ + int32_t upper_edge; /* in device coordinates */ struct { bool h, v; } active; @@ -560,6 +571,21 @@ struct tp_dispatch { struct evdev_device *tablet_device; bool tablet_left_handed_state; } left_handed; + + struct { + enum tp_edge_motion_state state; + + usec_t last_motion_time; + + uint32_t current_edge; + + double motion_dx; + double motion_dy; + + uint32_t continuous_motion_count; + + struct libinput_timer timer; + } edge_motion; }; static inline struct tp_dispatch * @@ -721,6 +747,15 @@ tp_edge_scroll_touch_active(const struct tp_dispatch *tp, const struct tp_touch uint32_t tp_touch_get_edge(const struct tp_dispatch *tp, const struct tp_touch *t); +void +tp_init_edge_motion(struct tp_dispatch *tp); + +int +tp_edge_motion_handle_drag_state(struct tp_dispatch *tp, usec_t time); + +void +tp_remove_edge_motion(struct tp_dispatch *tp); + void tp_init_gesture(struct tp_dispatch *tp);