Merge branch 'edge-motion' into 'main'

Implements Edge motion

See merge request libinput/libinput!1438
This commit is contained in:
Suraj Nagisetty 2026-03-18 05:20:54 +00:00
commit aae6214e3a
5 changed files with 264 additions and 1 deletions

View file

@ -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',

View file

@ -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 <math.h>
#include <string.h>
#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);
}

View file

@ -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

View file

@ -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);

View file

@ -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);