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,25 +48,69 @@ 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 */
52+ int abs_x_max , abs_y_max ; /* Maximum value for ABS_X/ABS_Y from device */
53+ bool abs_range_initialized ; /* Whether ABS range has been queried */
54+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
55+ int smooth_x , smooth_y ; /* Smoothed coordinates for ABS events */
56+ bool smooth_initialized ; /* Whether smoothing has been initialized */
57+ #endif
3958} twin_linux_input_t ;
4059
4160static void check_mouse_bounds (twin_linux_input_t * tm )
4261{
62+ /* Guard against zero-sized screens */
63+ if (tm -> screen -> width == 0 || tm -> screen -> height == 0 ) {
64+ tm -> x = tm -> y = 0 ;
65+ return ;
66+ }
67+
4368 if (tm -> x < 0 )
4469 tm -> x = 0 ;
45- if (tm -> x > tm -> screen -> width )
46- tm -> x = tm -> screen -> width ;
70+ if (tm -> x >= tm -> screen -> width )
71+ tm -> x = tm -> screen -> width - 1 ;
4772 if (tm -> y < 0 )
4873 tm -> y = 0 ;
49- if (tm -> y > tm -> screen -> height )
50- tm -> y = tm -> screen -> height ;
74+ if (tm -> y >= tm -> screen -> height )
75+ tm -> y = tm -> screen -> height - 1 ;
76+ }
77+
78+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
79+ /* Apply weighted moving average smoothing to reduce jitter
80+ * Formula: smooth = (smooth * weight + raw) / (weight + 1)
81+ * Higher weight = more smoothing but more latency
82+ */
83+ static inline void smooth_abs_coords (twin_linux_input_t * tm ,
84+ int raw_x ,
85+ int raw_y )
86+ {
87+ if (!tm -> smooth_initialized ) {
88+ /* Cold start: use raw values directly */
89+ tm -> smooth_x = raw_x ;
90+ tm -> smooth_y = raw_y ;
91+ tm -> smooth_initialized = true;
92+ } else {
93+ /* Weighted moving average with 64-bit intermediates to prevent overflow
94+ */
95+ int64_t acc_x =
96+ (int64_t ) tm -> smooth_x * TWIN_INPUT_SMOOTH_WEIGHT + raw_x ;
97+ int64_t acc_y =
98+ (int64_t ) tm -> smooth_y * TWIN_INPUT_SMOOTH_WEIGHT + raw_y ;
99+ tm -> smooth_x = (int ) (acc_x / (TWIN_INPUT_SMOOTH_WEIGHT + 1 ));
100+ tm -> smooth_y = (int ) (acc_y / (TWIN_INPUT_SMOOTH_WEIGHT + 1 ));
101+ }
102+
103+ tm -> x = tm -> smooth_x ;
104+ tm -> y = tm -> smooth_y ;
51105}
106+ #endif
52107
53108static void twin_linux_input_events (struct input_event * ev ,
54109 twin_linux_input_t * tm )
55110{
56111 /* TODO: twin_screen_dispatch() should be protect by mutex_lock, but the
57- * counterpart piece of code with mutex is not yet sure */
112+ * counterpart piece of code with mutex is not yet sure.
113+ */
58114
59115 twin_event_t tev ;
60116
@@ -79,22 +135,56 @@ static void twin_linux_input_events(struct input_event *ev,
79135 }
80136 break ;
81137 case EV_ABS :
138+ /* Scale absolute coordinates to screen resolution.
139+ * The range is dynamically queried from each device using EVIOCGABS.
140+ * Formula: scaled = ((value - min) * (screen_size - 1)) / (max - min)
141+ * This correctly maps device coordinates [min, max] to screen [0,
142+ * size-1]
143+ */
82144 if (ev -> code == ABS_X ) {
83- tm -> x = ev -> value ;
84- check_mouse_bounds (tm );
85- tev .kind = TwinEventMotion ;
86- tev .u .pointer .screen_x = tm -> x ;
87- tev .u .pointer .screen_y = tm -> y ;
88- tev .u .pointer .button = tm -> btns ;
89- twin_screen_dispatch (tm -> screen , & tev );
145+ int range = tm -> abs_x_max - tm -> abs_x_min ;
146+ if (range > 0 ) {
147+ int raw_x = ((int64_t ) (ev -> value - tm -> abs_x_min ) *
148+ (tm -> screen -> width - 1 )) /
149+ range ;
150+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
151+ /* Apply smoothing to X axis, keep Y unchanged */
152+ smooth_abs_coords (tm , raw_x , tm -> y );
153+ #else
154+ tm -> x = raw_x ;
155+ #endif
156+ check_mouse_bounds (tm );
157+ tev .kind = TwinEventMotion ;
158+ tev .u .pointer .screen_x = tm -> x ;
159+ tev .u .pointer .screen_y = tm -> y ;
160+ tev .u .pointer .button = tm -> btns ;
161+ twin_screen_dispatch (tm -> screen , & tev );
162+ } else {
163+ log_warn ("Ignoring ABS_X event: invalid range [%d,%d]" ,
164+ tm -> abs_x_min , tm -> abs_x_max );
165+ }
90166 } else if (ev -> code == ABS_Y ) {
91- tm -> y = ev -> value ;
92- check_mouse_bounds (tm );
93- tev .kind = TwinEventMotion ;
94- tev .u .pointer .screen_x = tm -> x ;
95- tev .u .pointer .screen_y = tm -> y ;
96- tev .u .pointer .button = tm -> btns ;
97- twin_screen_dispatch (tm -> screen , & tev );
167+ int range = tm -> abs_y_max - tm -> abs_y_min ;
168+ if (range > 0 ) {
169+ int raw_y = ((int64_t ) (ev -> value - tm -> abs_y_min ) *
170+ (tm -> screen -> height - 1 )) /
171+ range ;
172+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
173+ /* Apply smoothing to Y axis, keep X unchanged */
174+ smooth_abs_coords (tm , tm -> x , raw_y );
175+ #else
176+ tm -> y = raw_y ;
177+ #endif
178+ check_mouse_bounds (tm );
179+ tev .kind = TwinEventMotion ;
180+ tev .u .pointer .screen_x = tm -> x ;
181+ tev .u .pointer .screen_y = tm -> y ;
182+ tev .u .pointer .button = tm -> btns ;
183+ twin_screen_dispatch (tm -> screen , & tev );
184+ } else {
185+ log_warn ("Ignoring ABS_Y event: invalid range [%d,%d]" ,
186+ tm -> abs_y_min , tm -> abs_y_max );
187+ }
98188 }
99189 break ;
100190 case EV_KEY :
@@ -169,6 +259,57 @@ static bool twin_linux_udev_update(struct udev_monitor *mon)
169259 return false;
170260}
171261
262+ /* Query absolute axis information from an input device */
263+ static void twin_linux_input_query_abs (int fd , twin_linux_input_t * tm )
264+ {
265+ struct input_absinfo abs_info ;
266+
267+ /* Query ABS_X range (minimum and maximum) */
268+ if (ioctl (fd , EVIOCGABS (ABS_X ), & abs_info ) == 0 ) {
269+ /* Validate range: maximum must be greater than minimum */
270+ if (abs_info .maximum > abs_info .minimum ) {
271+ int range = abs_info .maximum - abs_info .minimum ;
272+ int current_range = tm -> abs_x_max - tm -> abs_x_min ;
273+
274+ /* Accept first device range unconditionally, or larger ranges from
275+ * subsequent devices
276+ */
277+ if (!tm -> abs_range_initialized || range > current_range ) {
278+ log_info ("ABS_X range updated: [%d,%d] (device fd=%d)" ,
279+ abs_info .minimum , abs_info .maximum , fd );
280+ tm -> abs_x_min = abs_info .minimum ;
281+ tm -> abs_x_max = abs_info .maximum ;
282+ tm -> abs_range_initialized = true;
283+ }
284+ } else {
285+ log_warn ("Device fd=%d: ABS_X range invalid [%d,%d]" , fd ,
286+ abs_info .minimum , abs_info .maximum );
287+ }
288+ }
289+
290+ /* Query ABS_Y range (minimum and maximum) */
291+ if (ioctl (fd , EVIOCGABS (ABS_Y ), & abs_info ) == 0 ) {
292+ /* Validate range: maximum must be greater than minimum */
293+ if (abs_info .maximum > abs_info .minimum ) {
294+ int range = abs_info .maximum - abs_info .minimum ;
295+ int current_range = tm -> abs_y_max - tm -> abs_y_min ;
296+
297+ /* Accept first device range unconditionally, or larger ranges from
298+ * subsequent devices
299+ */
300+ if (!tm -> abs_range_initialized || range > current_range ) {
301+ log_info ("ABS_Y range updated: [%d,%d] (device fd=%d)" ,
302+ abs_info .minimum , abs_info .maximum , fd );
303+ tm -> abs_y_min = abs_info .minimum ;
304+ tm -> abs_y_max = abs_info .maximum ;
305+ }
306+ } else {
307+ log_warn ("Device fd=%d: ABS_Y range invalid [%d,%d]" , fd ,
308+ abs_info .minimum , abs_info .maximum );
309+ }
310+ }
311+ }
312+
172313static void twin_linux_edev_open (struct pollfd * pfds , twin_linux_input_t * tm )
173314{
174315 /* New event device list */
@@ -200,23 +341,39 @@ static void twin_linux_edev_open(struct pollfd *pfds, twin_linux_input_t *tm)
200341
201342 /* Open the file if it is not on the list */
202343 int fd = open (evdev_name , O_RDWR | O_NONBLOCK );
203- if (fd > 0 && !opened ) {
344+ if (fd >= 0 && !opened ) {
345+ /* Query absolute axis info for newly opened devices */
346+ twin_linux_input_query_abs (fd , tm );
204347 evdevs [new_evdev_cnt ].idx = i ;
205348 evdevs [new_evdev_cnt ].fd = fd ;
206349 new_evdev_cnt ++ ;
207350 }
208351 }
209352
210353 /* Close disconnected devices */
354+ bool device_list_changed = false;
211355 for (size_t i = 0 ; i < tm -> evdev_cnt ; i ++ ) {
212- if (tm -> evdevs [i ].fd > 0 )
356+ if (tm -> evdevs [i ].fd > 0 ) {
213357 close (tm -> evdevs [i ].fd );
358+ device_list_changed = true;
359+ }
214360 }
215361
362+ /* Check if new devices were added */
363+ if (new_evdev_cnt != tm -> evdev_cnt )
364+ device_list_changed = true;
365+
216366 /* Overwrite the evdev list */
217367 memcpy (tm -> evdevs , evdevs , sizeof (tm -> evdevs ));
218368 tm -> evdev_cnt = new_evdev_cnt ;
219369
370+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
371+ /* Reset smoothing state on device reconnection to prevent coordinate jumps
372+ */
373+ if (device_list_changed && tm -> smooth_initialized )
374+ tm -> smooth_initialized = false;
375+ #endif
376+
220377 /* Initialize evdev poll file descriptors */
221378 for (size_t i = tm -> udev_cnt ; i < tm -> evdev_cnt + tm -> udev_cnt ; i ++ ) {
222379 pfds [i ].fd = tm -> evdevs [i - 1 ].fd ;
@@ -293,10 +450,25 @@ void *twin_linux_input_create(twin_screen_t *screen)
293450
294451 tm -> screen = screen ;
295452
453+ /* Initialize ABS axis ranges to common touchscreen default.
454+ * These will be updated to actual device values when devices are opened.
455+ */
456+ tm -> abs_x_min = tm -> abs_y_min = 0 ;
457+ tm -> abs_x_max = tm -> abs_y_max = 32767 ;
458+ log_info (
459+ "Input system initialized: screen=%dx%d, default ABS range=[%d,%d]" ,
460+ screen -> width , screen -> height , tm -> abs_x_min , tm -> abs_x_max );
461+
296462 /* Centering the cursor position */
297463 tm -> x = screen -> width / 2 ;
298464 tm -> y = screen -> height / 2 ;
299465
466+ #if TWIN_INPUT_SMOOTH_WEIGHT > 0
467+ /* Initialize smoothing from center position */
468+ tm -> smooth_x = tm -> x , tm -> smooth_y = tm -> y ;
469+ tm -> smooth_initialized = true;
470+ #endif
471+
300472 /* Start event handling thread */
301473 if (pthread_create (& tm -> evdev_thread , NULL , twin_linux_evdev_thread , tm )) {
302474 log_error ("Failed to create evdev thread" );
0 commit comments