Skip to content

Commit c9f462c

Browse files
authored
Merge pull request #140 from sysprog21/fbdev-screen
Fix initial rendering and mouse scaling
2 parents 35d3289 + 721bb44 commit c9f462c

File tree

2 files changed

+210
-22
lines changed

2 files changed

+210
-22
lines changed

backend/fbdev.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ typedef struct {
3939
uint16_t cmap[3][256];
4040
uint8_t *fb_base;
4141
size_t fb_len;
42+
43+
/* Initialization state */
44+
bool first_run; /* Track first work queue execution for initial damage */
4245
} twin_fbdev_t;
4346

4447
/* color conversion */
@@ -201,6 +204,16 @@ static bool twin_fbdev_work(void *closure)
201204
twin_fbdev_t *tx = PRIV(closure);
202205
twin_screen_t *screen = SCREEN(closure);
203206

207+
/* Mark entire screen as damaged on first run to ensure initial rendering.
208+
* This is necessary because the screen content is not automatically
209+
* rendered when the application starts. Using per-context state allows
210+
* proper reinitialization if the backend is torn down and recreated.
211+
*/
212+
if (tx->first_run) {
213+
tx->first_run = false;
214+
twin_screen_damage(screen, 0, 0, screen->width, screen->height);
215+
}
216+
204217
if (tx->vt_active && (tx->fb_base != MAP_FAILED)) {
205218
/* Unmap the fbdev */
206219
munmap(tx->fb_base, tx->fb_len);
@@ -240,6 +253,9 @@ twin_context_t *twin_fbdev_init(int width, int height)
240253

241254
twin_fbdev_t *tx = ctx->priv;
242255

256+
/* Initialize first_run flag for initial screen damage */
257+
tx->first_run = true;
258+
243259
/* Open the framebuffer device */
244260
tx->fb_fd = open(fbdev_path, O_RDWR);
245261
if (tx->fb_fd == -1) {

backend/linux_input.c

Lines changed: 194 additions & 22 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,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

4160
static 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

53108
static 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+
172313
static 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

Comments
 (0)