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