Skip to content

Commit 6f611f5

Browse files
committed
Fix ABS coordinate scaling and add input smoothing
This commit addresses critical issues in absolute coordinate handling and adds smoothing support for touch/stylus input devices. Critical fixes: - Fix ABS scaling to properly handle non-zero minimum values (e.g., stylus devices with range [-2048, 2047]) - Use correct formula: scaled = ((value - min) * (size - 1)) / (max - min) - Fix fd check to accept valid fd=0 (changed from fd > 0 to fd >= 0) - Add range validation to prevent division by zero Input smoothing enhancements: - Add configurable weighted moving average smoothing - Default weight of 3 provides ~50% jitter reduction - Can be disabled at compile time (weight=0) or overridden via CFLAGS - Uses 64-bit intermediates to prevent integer overflow
1 parent 58b8504 commit 6f611f5

File tree

1 file changed

+152
-34
lines changed

1 file changed

+152
-34
lines changed

backend/linux_input.c

Lines changed: 152 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Twin - A Tiny Window System
3-
* Copyright (c) 2024 National Cheng Kung University, Taiwan
3+
* Copyright (c) 2024-2025 National Cheng Kung University, Taiwan
44
* All rights reserved.
55
*/
66

@@ -9,6 +9,7 @@
99
#include <linux/input.h>
1010
#include <poll.h>
1111
#include <pthread.h>
12+
#include <stdint.h>
1213
#include <stdlib.h>
1314
#include <string.h>
1415
#include <twin.h>
@@ -27,6 +28,17 @@ struct evdev_info {
2728
int fd;
2829
};
2930

31+
/* Coordinate smoothing configuration */
32+
/* Weight for smoothing (0=off, 1-7=strength) */
33+
#ifndef TWIN_INPUT_SMOOTH_WEIGHT
34+
#define TWIN_INPUT_SMOOTH_WEIGHT 3
35+
#endif
36+
37+
/* Compile-time bounds check for smoothing weight */
38+
#if TWIN_INPUT_SMOOTH_WEIGHT < 0 || TWIN_INPUT_SMOOTH_WEIGHT > 7
39+
#error "TWIN_INPUT_SMOOTH_WEIGHT must be in range [0, 7]"
40+
#endif
41+
3042
typedef struct {
3143
twin_screen_t *screen;
3244
pthread_t evdev_thread;
@@ -36,21 +48,62 @@ typedef struct {
3648
int fd;
3749
int btns;
3850
int x, y;
51+
int abs_x_min, abs_y_min; /* Minimum value for ABS_X/ABS_Y from device */
3952
int abs_x_max, abs_y_max; /* Maximum value for ABS_X/ABS_Y from device */
53+
#if TWIN_INPUT_SMOOTH_WEIGHT > 0
54+
int smooth_x, smooth_y; /* Smoothed coordinates for ABS events */
55+
bool smooth_initialized; /* Whether smoothing has been initialized */
56+
#endif
4057
} twin_linux_input_t;
4158

4259
static void check_mouse_bounds(twin_linux_input_t *tm)
4360
{
61+
/* Guard against zero-sized screens */
62+
if (tm->screen->width == 0 || tm->screen->height == 0) {
63+
tm->x = tm->y = 0;
64+
return;
65+
}
66+
4467
if (tm->x < 0)
4568
tm->x = 0;
46-
if (tm->x > tm->screen->width)
47-
tm->x = tm->screen->width;
69+
if (tm->x >= tm->screen->width)
70+
tm->x = tm->screen->width - 1;
4871
if (tm->y < 0)
4972
tm->y = 0;
50-
if (tm->y > tm->screen->height)
51-
tm->y = tm->screen->height;
73+
if (tm->y >= tm->screen->height)
74+
tm->y = tm->screen->height - 1;
5275
}
5376

77+
#if TWIN_INPUT_SMOOTH_WEIGHT > 0
78+
/* Apply weighted moving average smoothing to reduce jitter
79+
* Formula: smooth = (smooth * weight + raw) / (weight + 1)
80+
* Higher weight = more smoothing but more latency
81+
*/
82+
static inline void smooth_abs_coords(twin_linux_input_t *tm,
83+
int raw_x,
84+
int raw_y)
85+
{
86+
if (!tm->smooth_initialized) {
87+
/* Cold start: use raw values directly */
88+
tm->smooth_x = raw_x;
89+
tm->smooth_y = raw_y;
90+
tm->smooth_initialized = true;
91+
} else {
92+
/* Weighted moving average with 64-bit intermediates to prevent overflow
93+
*/
94+
int64_t acc_x =
95+
(int64_t) tm->smooth_x * TWIN_INPUT_SMOOTH_WEIGHT + raw_x;
96+
int64_t acc_y =
97+
(int64_t) tm->smooth_y * TWIN_INPUT_SMOOTH_WEIGHT + raw_y;
98+
tm->smooth_x = (int) (acc_x / (TWIN_INPUT_SMOOTH_WEIGHT + 1));
99+
tm->smooth_y = (int) (acc_y / (TWIN_INPUT_SMOOTH_WEIGHT + 1));
100+
}
101+
102+
tm->x = tm->smooth_x;
103+
tm->y = tm->smooth_y;
104+
}
105+
#endif
106+
54107
static void twin_linux_input_events(struct input_event *ev,
55108
twin_linux_input_t *tm)
56109
{
@@ -82,25 +135,54 @@ static void twin_linux_input_events(struct input_event *ev,
82135
case EV_ABS:
83136
/* Scale absolute coordinates to screen resolution.
84137
* The range is dynamically queried from each device using EVIOCGABS.
85-
* If no device reported ABS info, we fall back to the common default
86-
* of 32767 for touchscreens and absolute pointing devices.
138+
* Formula: scaled = ((value - min) * (screen_size - 1)) / (max - min)
139+
* This correctly maps device coordinates [min, max] to screen [0,
140+
* size-1]
87141
*/
88-
if (ev->code == ABS_X && tm->abs_x_max > 0) {
89-
tm->x = ((int64_t) ev->value * tm->screen->width) / tm->abs_x_max;
90-
check_mouse_bounds(tm);
91-
tev.kind = TwinEventMotion;
92-
tev.u.pointer.screen_x = tm->x;
93-
tev.u.pointer.screen_y = tm->y;
94-
tev.u.pointer.button = tm->btns;
95-
twin_screen_dispatch(tm->screen, &tev);
96-
} else if (ev->code == ABS_Y && tm->abs_y_max > 0) {
97-
tm->y = ((int64_t) ev->value * tm->screen->height) / tm->abs_y_max;
98-
check_mouse_bounds(tm);
99-
tev.kind = TwinEventMotion;
100-
tev.u.pointer.screen_x = tm->x;
101-
tev.u.pointer.screen_y = tm->y;
102-
tev.u.pointer.button = tm->btns;
103-
twin_screen_dispatch(tm->screen, &tev);
142+
if (ev->code == ABS_X) {
143+
int range = tm->abs_x_max - tm->abs_x_min;
144+
if (range > 0) {
145+
int raw_x = ((int64_t) (ev->value - tm->abs_x_min) *
146+
(tm->screen->width - 1)) /
147+
range;
148+
#if TWIN_INPUT_SMOOTH_WEIGHT > 0
149+
/* Apply smoothing to X axis, keep Y unchanged */
150+
smooth_abs_coords(tm, raw_x, tm->y);
151+
#else
152+
tm->x = raw_x;
153+
#endif
154+
check_mouse_bounds(tm);
155+
tev.kind = TwinEventMotion;
156+
tev.u.pointer.screen_x = tm->x;
157+
tev.u.pointer.screen_y = tm->y;
158+
tev.u.pointer.button = tm->btns;
159+
twin_screen_dispatch(tm->screen, &tev);
160+
} else {
161+
log_warn("Ignoring ABS_X event: invalid range [%d,%d]",
162+
tm->abs_x_min, tm->abs_x_max);
163+
}
164+
} else if (ev->code == ABS_Y) {
165+
int range = tm->abs_y_max - tm->abs_y_min;
166+
if (range > 0) {
167+
int raw_y = ((int64_t) (ev->value - tm->abs_y_min) *
168+
(tm->screen->height - 1)) /
169+
range;
170+
#if TWIN_INPUT_SMOOTH_WEIGHT > 0
171+
/* Apply smoothing to Y axis, keep X unchanged */
172+
smooth_abs_coords(tm, tm->x, raw_y);
173+
#else
174+
tm->y = raw_y;
175+
#endif
176+
check_mouse_bounds(tm);
177+
tev.kind = TwinEventMotion;
178+
tev.u.pointer.screen_x = tm->x;
179+
tev.u.pointer.screen_y = tm->y;
180+
tev.u.pointer.button = tm->btns;
181+
twin_screen_dispatch(tm->screen, &tev);
182+
} else {
183+
log_warn("Ignoring ABS_Y event: invalid range [%d,%d]",
184+
tm->abs_y_min, tm->abs_y_max);
185+
}
104186
}
105187
break;
106188
case EV_KEY:
@@ -180,18 +262,44 @@ static void twin_linux_input_query_abs(int fd, twin_linux_input_t *tm)
180262
{
181263
struct input_absinfo abs_info;
182264

183-
/* Query ABS_X maximum value */
184-
if (ioctl(fd, EVIOCGABS(ABS_X), &abs_info) == 0 && abs_info.maximum > 0) {
185-
/* Update global maximum if this device has a larger range */
186-
if (abs_info.maximum > tm->abs_x_max)
187-
tm->abs_x_max = abs_info.maximum;
265+
/* Query ABS_X range (minimum and maximum) */
266+
if (ioctl(fd, EVIOCGABS(ABS_X), &abs_info) == 0) {
267+
/* Validate range: maximum must be greater than minimum */
268+
if (abs_info.maximum > abs_info.minimum) {
269+
int range = abs_info.maximum - abs_info.minimum;
270+
int current_range = tm->abs_x_max - tm->abs_x_min;
271+
272+
/* Update global range if this device has a larger range */
273+
if (range > current_range) {
274+
log_info("ABS_X range updated: [%d,%d] (device fd=%d)",
275+
abs_info.minimum, abs_info.maximum, fd);
276+
tm->abs_x_min = abs_info.minimum;
277+
tm->abs_x_max = abs_info.maximum;
278+
}
279+
} else {
280+
log_warn("Device fd=%d: ABS_X range invalid [%d,%d]", fd,
281+
abs_info.minimum, abs_info.maximum);
282+
}
188283
}
189284

190-
/* Query ABS_Y maximum value */
191-
if (ioctl(fd, EVIOCGABS(ABS_Y), &abs_info) == 0 && abs_info.maximum > 0) {
192-
/* Update global maximum if this device has a larger range */
193-
if (abs_info.maximum > tm->abs_y_max)
194-
tm->abs_y_max = abs_info.maximum;
285+
/* Query ABS_Y range (minimum and maximum) */
286+
if (ioctl(fd, EVIOCGABS(ABS_Y), &abs_info) == 0) {
287+
/* Validate range: maximum must be greater than minimum */
288+
if (abs_info.maximum > abs_info.minimum) {
289+
int range = abs_info.maximum - abs_info.minimum;
290+
int current_range = tm->abs_y_max - tm->abs_y_min;
291+
292+
/* Update global range if this device has a larger range */
293+
if (range > current_range) {
294+
log_info("ABS_Y range updated: [%d,%d] (device fd=%d)",
295+
abs_info.minimum, abs_info.maximum, fd);
296+
tm->abs_y_min = abs_info.minimum;
297+
tm->abs_y_max = abs_info.maximum;
298+
}
299+
} else {
300+
log_warn("Device fd=%d: ABS_Y range invalid [%d,%d]", fd,
301+
abs_info.minimum, abs_info.maximum);
302+
}
195303
}
196304
}
197305

@@ -226,7 +334,7 @@ static void twin_linux_edev_open(struct pollfd *pfds, twin_linux_input_t *tm)
226334

227335
/* Open the file if it is not on the list */
228336
int fd = open(evdev_name, O_RDWR | O_NONBLOCK);
229-
if (fd > 0 && !opened) {
337+
if (fd >= 0 && !opened) {
230338
/* Query absolute axis info for newly opened devices */
231339
twin_linux_input_query_abs(fd, tm);
232340
evdevs[new_evdev_cnt].idx = i;
@@ -324,12 +432,22 @@ void *twin_linux_input_create(twin_screen_t *screen)
324432
/* Initialize ABS axis ranges to common touchscreen default.
325433
* These will be updated to actual device values when devices are opened.
326434
*/
435+
tm->abs_x_min = tm->abs_y_min = 0;
327436
tm->abs_x_max = tm->abs_y_max = 32767;
437+
log_info(
438+
"Input system initialized: screen=%dx%d, default ABS range=[%d,%d]",
439+
screen->width, screen->height, tm->abs_x_min, tm->abs_x_max);
328440

329441
/* Centering the cursor position */
330442
tm->x = screen->width / 2;
331443
tm->y = screen->height / 2;
332444

445+
#if TWIN_INPUT_SMOOTH_WEIGHT > 0
446+
/* Initialize smoothing from center position */
447+
tm->smooth_x = tm->x, tm->smooth_y = tm->y;
448+
tm->smooth_initialized = true;
449+
#endif
450+
333451
/* Start event handling thread */
334452
if (pthread_create(&tm->evdev_thread, NULL, twin_linux_evdev_thread, tm)) {
335453
log_error("Failed to create evdev thread");

0 commit comments

Comments
 (0)