From 48c86a275bf8f07fbd1ed2f9ca100c109aae846c Mon Sep 17 00:00:00 2001 From: suraj Date: Wed, 4 Mar 2026 20:52:21 +0530 Subject: [PATCH] Implements Edge motion Hook into the tap-and-drag state machine to emit pointer events when the finger reaches the touchpad edge. This allows continued motion when dragging beyond the physical boundary. Signed-off-by: suraj --- meson.build | 1 + src/evdev-mt-touchpad-edge-motion.c | 246 ++++++++++++++++++++++++++++ src/evdev-mt-touchpad-tap.c | 4 + src/evdev-mt-touchpad.h | 12 +- 4 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 src/evdev-mt-touchpad-edge-motion.c diff --git a/meson.build b/meson.build index 235a67a5..2192a196 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..2a40b570 --- /dev/null +++ b/src/evdev-mt-touchpad-edge-motion.c @@ -0,0 +1,246 @@ +/* + * Copyright © 2014-2015 Red Hat, 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" + +void +tp_edge_motion_init(struct tp_dispatch *tp); + +enum edge_motion_state { STATE_IDLE, STATE_DRAG_ACTIVE, STATE_EDGE_MOTION }; + +struct edge_motion_fsm { + enum edge_motion_state current_state; + usec_t last_motion_time; + uint32_t current_edge; + double motion_dx; + double motion_dy; + uint32_t continuous_motion_count; + struct tp_dispatch *tp; + struct libinput_timer timer; +}; + +static struct edge_motion_fsm fsm = { + .current_state = STATE_IDLE, + .tp = NULL, +}; + +#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 = sqrt((*dx) * (*dx) + (*dy) * (*dy)); + if (mag > 0) { + *dx /= mag; + *dy /= mag; + } +} + +static void +inject_accumulated_motion(struct tp_dispatch *tp, usec_t time) +{ + if (usec_is_zero(fsm.last_motion_time)) { + fsm.last_motion_time = time; + return; + } + + usec_t time_since_last = usec_sub(time, fsm.last_motion_time); + double dist_mm = + EDGE_MOTION_CONFIG_SPEED_MM_S * ((double)usec_as_uint64_t(time_since_last) / 1000000.0); + + if (dist_mm < 0.001) + return; + + struct device_float_coords raw = { + .x = fsm.motion_dx * dist_mm * tp->accel.x_scale_coeff, + .y = fsm.motion_dy * dist_mm * tp->accel.y_scale_coeff + }; + + struct normalized_coords delta = + filter_dispatch(tp->device->pointer.filter, &raw, tp, time); + + pointer_notify_motion(&tp->device->base, time, &delta, &raw); + + fsm.last_motion_time = time; + fsm.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 edge_motion_fsm *fsm_ptr = data; + + if (fsm_ptr->current_state != STATE_EDGE_MOTION) + return; + + inject_accumulated_motion(fsm_ptr->tp, now); + libinput_timer_set(&fsm_ptr->timer, usec_add(now, usec_from_uint64_t(EDGE_MOTION_CONFIG_MIN_INTERVAL_US))); +} + +void +tp_edge_motion_init(struct tp_dispatch *tp) +{ + if (fsm.tp) + return; + + memset(&fsm, 0, sizeof(fsm)); + fsm.current_state = STATE_IDLE; + fsm.tp = tp; + + libinput_timer_init(&fsm.timer, + tp_libinput_context(tp), + "edge drag motion", + tp_edge_motion_handle_timeout, + &fsm); +} + +void +tp_edge_motion_cleanup(void) +{ + if (fsm.tp) + libinput_timer_destroy(&fsm.timer); + + memset(&fsm, 0, sizeof(fsm)); + fsm.current_state = STATE_IDLE; +} + +int +tp_edge_motion_handle_drag_state(struct tp_dispatch *tp, usec_t time) +{ + if (!fsm.tp) + tp_edge_motion_init(tp); + + bool drag_active = false; + + switch (tp->tap.state) { + case TAP_STATE_1FGTAP_DRAGGING: + case TAP_STATE_1FGTAP_DRAGGING_2: + case TAP_STATE_1FGTAP_DRAGGING_WAIT: + case TAP_STATE_1FGTAP_DRAGGING_OR_TAP: + case TAP_STATE_1FGTAP_DRAGGING_OR_DOUBLETAP: + drag_active = true; + break; + default: + drag_active = false; + break; + } + + 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 edge_motion_state next_state = STATE_IDLE; + if (drag_active) { + next_state = (detected_edge != EDGE_NONE) ? STATE_EDGE_MOTION + : STATE_DRAG_ACTIVE; + } + + if (next_state != fsm.current_state) { + fsm.current_state = next_state; + fsm.current_edge = detected_edge; + + if (fsm.current_state != STATE_EDGE_MOTION) + fsm.continuous_motion_count = 0; + + switch (fsm.current_state) { + case STATE_IDLE: + case STATE_DRAG_ACTIVE: + libinput_timer_cancel(&fsm.timer); + break; + + case STATE_EDGE_MOTION: + calculate_motion_vector(fsm.current_edge, + &fsm.motion_dx, + &fsm.motion_dy); + fsm.last_motion_time = time; + tp_edge_motion_handle_timeout(time, &fsm); + break; + } + } else if (fsm.current_state == STATE_EDGE_MOTION && + detected_edge != fsm.current_edge) { + fsm.current_edge = detected_edge; + calculate_motion_vector(fsm.current_edge, + &fsm.motion_dx, + &fsm.motion_dy); + } + + return (fsm.current_state == STATE_EDGE_MOTION); +} diff --git a/src/evdev-mt-touchpad-tap.c b/src/evdev-mt-touchpad-tap.c index 596ba944..ebde0ab8 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 @@ -1594,6 +1597,7 @@ void tp_remove_tap(struct tp_dispatch *tp) { libinput_timer_cancel(&tp->tap.timer); + tp_edge_motion_cleanup(); } void diff --git a/src/evdev-mt-touchpad.h b/src/evdev-mt-touchpad.h index 7e85baf7..6331d2d5 100644 --- a/src/evdev-mt-touchpad.h +++ b/src/evdev-mt-touchpad.h @@ -140,11 +140,13 @@ enum tp_tap_touch_state { TAP_TOUCH_STATE_DEAD, /**< exceeded motion/timeout */ }; -/* For edge scrolling, so we only care about right and bottom */ +/* 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 +423,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; @@ -721,6 +725,12 @@ 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); +int +tp_edge_motion_handle_drag_state(struct tp_dispatch *tp, usec_t time); + +void +tp_edge_motion_cleanup(void); + void tp_init_gesture(struct tp_dispatch *tp);