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
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+
3042typedef 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
4259static 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+
54107static 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