From 332c3b2b6d50657dfaeea0581f398e8a476a86d8 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 28 Oct 2025 10:49:46 +0100 Subject: [PATCH 01/17] input: Separate init of keyboard specific struct Prepare work to support other input devices like mouse, touchpad... Signed-off-by: Jocelyn Falempe --- src/uterm_input.c | 82 +++++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/src/uterm_input.c b/src/uterm_input.c index 5e64dfea..af4dc687 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -141,6 +141,48 @@ static void input_sleep_dev(struct uterm_input_dev *dev) dev->rfd = -1; } +static int input_init_keyboard(struct uterm_input_dev *dev) +{ + dev->num_syms = 1; + dev->event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->event.keysyms) + return -1; + dev->event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->event.codepoints) + goto err_syms; + dev->repeat_event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->repeat_event.keysyms) + goto err_codepoints; + dev->repeat_event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); + if (!dev->repeat_event.codepoints) + goto err_rsyms; + + if (uxkb_dev_init(dev)) + goto err_rcodepoints; + + return 0; + +err_rcodepoints: + free(dev->repeat_event.codepoints); +err_rsyms: + free(dev->repeat_event.keysyms); +err_codepoints: + free(dev->event.codepoints); +err_syms: + free(dev->event.keysyms); + return -1; +} + +static void input_exit_keyboard(struct uterm_input_dev *dev) +{ + uxkb_dev_destroy(dev); + free(dev->repeat_event.codepoints); + free(dev->repeat_event.keysyms); + free(dev->event.codepoints); + free(dev->event.keysyms); +} + + static void input_new_dev(struct uterm_input *input, const char *node, unsigned int capabilities) @@ -160,23 +202,11 @@ static void input_new_dev(struct uterm_input *input, if (!dev->node) goto err_free; - dev->num_syms = 1; - dev->event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->event.keysyms) - goto err_node; - dev->event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->event.codepoints) - goto err_syms; - dev->repeat_event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->repeat_event.keysyms) - goto err_codepoints; - dev->repeat_event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms); - if (!dev->repeat_event.codepoints) - goto err_rsyms; - - ret = uxkb_dev_init(dev); - if (ret) - goto err_rcodepoints; + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) { + ret = input_init_keyboard(dev); + if (ret) + goto err_node; + } if (input->awake > 0) { ret = input_wake_up_dev(dev); @@ -189,15 +219,8 @@ static void input_new_dev(struct uterm_input *input, return; err_kbd: - uxkb_dev_destroy(dev); -err_rcodepoints: - free(dev->repeat_event.codepoints); -err_rsyms: - free(dev->repeat_event.keysyms); -err_codepoints: - free(dev->event.codepoints); -err_syms: - free(dev->event.keysyms); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + input_exit_keyboard(dev); err_node: free(dev->node); err_free: @@ -209,11 +232,8 @@ static void input_free_dev(struct uterm_input_dev *dev) llog_debug(dev->input, "free device %s", dev->node); input_sleep_dev(dev); shl_dlist_unlink(&dev->list); - uxkb_dev_destroy(dev); - free(dev->repeat_event.codepoints); - free(dev->repeat_event.keysyms); - free(dev->event.codepoints); - free(dev->event.keysyms); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + input_exit_keyboard(dev); free(dev->node); free(dev); } From bd1852091b4d9ae074aad89327d6f2d4d8315d08 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 28 Oct 2025 13:40:35 +0100 Subject: [PATCH 02/17] input: rename keyboard hook This prepare the work to have a different hook for pointer events Signed-off-by: Jocelyn Falempe --- src/uterm_input.c | 11 ++++++----- src/uterm_input_internal.h | 2 +- src/uterm_input_uxkb.c | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/uterm_input.c b/src/uterm_input.c index af4dc687..65a642a5 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -281,7 +281,7 @@ int uterm_input_new(struct uterm_input **out, input->repeat_rate = repeat_rate; shl_dlist_init(&input->devices); - ret = shl_hook_new(&input->hook); + ret = shl_hook_new(&input->key_hook); if (ret) goto err_free; @@ -308,7 +308,8 @@ int uterm_input_new(struct uterm_input **out, return 0; err_hook: - shl_hook_free(input->hook); + shl_hook_free(input->key_hook); + err_free: free(input); return ret; @@ -341,7 +342,7 @@ void uterm_input_unref(struct uterm_input *input) } uxkb_desc_destroy(input); - shl_hook_free(input->hook); + shl_hook_free(input->key_hook); ev_eloop_unref(input->eloop); free(input); } @@ -445,7 +446,7 @@ int uterm_input_register_cb(struct uterm_input *input, if (!input || !cb) return -EINVAL; - return shl_hook_add_cast(input->hook, cb, data, false); + return shl_hook_add_cast(input->key_hook, cb, data, false); } SHL_EXPORT @@ -456,7 +457,7 @@ void uterm_input_unregister_cb(struct uterm_input *input, if (!input || !cb) return; - shl_hook_rm_cast(input->hook, cb, data); + shl_hook_rm_cast(input->key_hook, cb, data); } SHL_EXPORT diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 816bfd20..013ee010 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -74,7 +74,7 @@ struct uterm_input { unsigned int repeat_rate; unsigned int repeat_delay; - struct shl_hook *hook; + struct shl_hook *key_hook; struct xkb_context *ctx; struct xkb_keymap *keymap; struct xkb_compose_table *compose_table; diff --git a/src/uterm_input_uxkb.c b/src/uterm_input_uxkb.c index 1d28a8da..79d89231 100644 --- a/src/uterm_input_uxkb.c +++ b/src/uterm_input_uxkb.c @@ -209,7 +209,7 @@ static void timer_event(struct ev_timer *timer, uint64_t num, void *data) struct uterm_input_dev *dev = data; dev->repeat_event.handled = false; - shl_hook_call(dev->input->hook, dev->input, &dev->repeat_event); + shl_hook_call(dev->input->key_hook, dev->input, &dev->repeat_event); } int uxkb_dev_init(struct uterm_input_dev *dev) @@ -532,7 +532,7 @@ int uxkb_dev_process(struct uterm_input_dev *dev, return -ENOKEY; dev->event.handled = false; - shl_hook_call(dev->input->hook, dev->input, &dev->event); + shl_hook_call(dev->input->key_hook, dev->input, &dev->event); return 0; } From 7d0881b4fe0da66bb77db44f870893d0a3464d16 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 28 Oct 2025 13:47:09 +0100 Subject: [PATCH 03/17] Rename struct and callback for keyboard. Signed-off-by: Jocelyn Falempe --- src/kmscon_seat.c | 8 ++++---- src/kmscon_terminal.c | 8 ++++---- src/uterm_input.c | 8 ++++---- src/uterm_input.h | 12 ++++++------ src/uterm_input_internal.h | 4 ++-- src/uterm_input_uxkb.c | 2 +- src/uterm_vt.c | 10 +++++----- tests/test_input.c | 6 +++--- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c index 692762bf..007ff370 100644 --- a/src/kmscon_seat.c +++ b/src/kmscon_seat.c @@ -577,7 +577,7 @@ static int seat_vt_event(struct uterm_vt *vt, struct uterm_vt_event *ev, } static void seat_input_event(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct kmscon_seat *seat = data; @@ -766,7 +766,7 @@ int kmscon_seat_new(struct kmscon_seat **out, if (ret) goto err_conf; - ret = uterm_input_register_cb(seat->input, seat_input_event, seat); + ret = uterm_input_register_key_cb(seat->input, seat_input_event, seat); if (ret) goto err_input; @@ -788,7 +788,7 @@ int kmscon_seat_new(struct kmscon_seat **out, return 0; err_input_cb: - uterm_input_unregister_cb(seat->input, seat_input_event, seat); + uterm_input_unregister_key_cb(seat->input, seat_input_event, seat); err_input: uterm_input_unref(seat->input); err_conf: @@ -834,7 +834,7 @@ void kmscon_seat_free(struct kmscon_seat *seat) } uterm_vt_deallocate(seat->vt); - uterm_input_unregister_cb(seat->input, seat_input_event, seat); + uterm_input_unregister_key_cb(seat->input, seat_input_event, seat); uterm_input_unref(seat->input); kmscon_conf_free(seat->conf_ctx); free(seat->name); diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 7fa8eb8e..d1b85eb9 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -500,7 +500,7 @@ static void rm_display(struct kmscon_terminal *term, struct uterm_display *disp) } static void input_event(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct kmscon_terminal *term = data; @@ -631,7 +631,7 @@ static void terminal_destroy(struct kmscon_terminal *term) terminal_close(term); rm_all_screens(term); - uterm_input_unregister_cb(term->input, input_event, term); + uterm_input_unregister_key_cb(term->input, input_event, term); ev_eloop_rm_fd(term->ptyfd); kmscon_pty_unref(term->pty); kmscon_font_unref(term->bold_font); @@ -792,7 +792,7 @@ int kmscon_terminal_register(struct kmscon_session **out, if (ret) goto err_pty; - ret = uterm_input_register_cb(term->input, input_event, term); + ret = uterm_input_register_key_cb(term->input, input_event, term); if (ret) goto err_ptyfd; @@ -810,7 +810,7 @@ int kmscon_terminal_register(struct kmscon_session **out, return 0; err_input: - uterm_input_unregister_cb(term->input, input_event, term); + uterm_input_unregister_key_cb(term->input, input_event, term); err_ptyfd: ev_eloop_rm_fd(term->ptyfd); err_pty: diff --git a/src/uterm_input.c b/src/uterm_input.c index 65a642a5..41061e0e 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -439,8 +439,8 @@ void uterm_input_remove_dev(struct uterm_input *input, const char *node) } SHL_EXPORT -int uterm_input_register_cb(struct uterm_input *input, - uterm_input_cb cb, +int uterm_input_register_key_cb(struct uterm_input *input, + uterm_input_key_cb cb, void *data) { if (!input || !cb) @@ -450,8 +450,8 @@ int uterm_input_register_cb(struct uterm_input *input, } SHL_EXPORT -void uterm_input_unregister_cb(struct uterm_input *input, - uterm_input_cb cb, +void uterm_input_unregister_key_cb(struct uterm_input *input, + uterm_input_key_cb cb, void *data) { if (!input || !cb) diff --git a/src/uterm_input.h b/src/uterm_input.h index 91d704bc..7eadca75 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -61,7 +61,7 @@ enum uterm_input_modifier { /* keep in sync with TSM_VTE_INVALID */ #define UTERM_INPUT_INVALID 0xffffffff -struct uterm_input_event { +struct uterm_input_key_event { bool handled; /* user-controlled, default is false */ uint16_t keycode; /* linux keycode - KEY_* - linux/input.h */ uint32_t ascii; /* ascii keysym for @keycode */ @@ -74,9 +74,9 @@ struct uterm_input_event { #define UTERM_INPUT_HAS_MODS(_ev, _mods) (((_ev)->mods & (_mods)) == (_mods)) -typedef void (*uterm_input_cb) (struct uterm_input *input, - struct uterm_input_event *ev, - void *data); +typedef void (*uterm_input_key_cb) (struct uterm_input *input, + struct uterm_input_key_event *ev, + void *data); int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, const char *model, const char *layout, const char *variant, @@ -90,9 +90,9 @@ void uterm_input_unref(struct uterm_input *input); void uterm_input_add_dev(struct uterm_input *input, const char *node); void uterm_input_remove_dev(struct uterm_input *input, const char *node); -int uterm_input_register_cb(struct uterm_input *input, uterm_input_cb cb, +int uterm_input_register_key_cb(struct uterm_input *input, uterm_input_key_cb cb, void *data); -void uterm_input_unregister_cb(struct uterm_input *input, uterm_input_cb cb, +void uterm_input_unregister_key_cb(struct uterm_input *input, uterm_input_key_cb cb, void *data); void uterm_input_sleep(struct uterm_input *input); diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 013ee010..ae71be01 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -58,8 +58,8 @@ struct uterm_input_dev { char key_state_bits[SHL_DIV_ROUND_UP(KEY_CNT, CHAR_BIT)]; unsigned int num_syms; - struct uterm_input_event event; - struct uterm_input_event repeat_event; + struct uterm_input_key_event event; + struct uterm_input_key_event repeat_event; bool repeating; struct ev_timer *repeat_timer; diff --git a/src/uterm_input_uxkb.c b/src/uterm_input_uxkb.c index 79d89231..926b6725 100644 --- a/src/uterm_input_uxkb.c +++ b/src/uterm_input_uxkb.c @@ -338,7 +338,7 @@ static inline int uxkb_dev_resize_event(struct uterm_input_dev *dev, size_t s) } static int uxkb_dev_fill_event(struct uterm_input_dev *dev, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, xkb_keycode_t code, int num_syms, const xkb_keysym_t *syms) diff --git a/src/uterm_vt.c b/src/uterm_vt.c index 55f7c57e..1760a429 100644 --- a/src/uterm_vt.c +++ b/src/uterm_vt.c @@ -573,7 +573,7 @@ static int real_deactivate(struct uterm_vt *vt) return -EINPROGRESS; } -static void real_input(struct uterm_vt *vt, struct uterm_input_event *ev) +static void real_input(struct uterm_vt *vt, struct uterm_input_key_event *ev) { int id; struct vt_stat vts; @@ -696,7 +696,7 @@ static int fake_deactivate(struct uterm_vt *vt) return vt_call_deactivate(vt, false); } -static void fake_input(struct uterm_vt *vt, struct uterm_input_event *ev) +static void fake_input(struct uterm_vt *vt, struct uterm_input_key_event *ev) { if (ev->handled) return; @@ -749,7 +749,7 @@ static void fake_close(struct uterm_vt *vt) */ static void vt_input(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { struct uterm_vt *vt = data; @@ -890,7 +890,7 @@ int uterm_vt_allocate(struct uterm_vt_master *vtm, if (ret) goto err_sig1; - ret = uterm_input_register_cb(vt->input, vt_input, vt); + ret = uterm_input_register_key_cb(vt->input, vt_input, vt); if (ret) goto err_sig2; @@ -930,7 +930,7 @@ int uterm_vt_allocate(struct uterm_vt_master *vtm, return 0; err_input: - uterm_input_unregister_cb(vt->input, vt_input, vt); + uterm_input_unregister_key_cb(vt->input, vt_input, vt); err_sig2: ev_eloop_unregister_signal_cb(vtm->eloop, SIGUSR2, vt_sigusr2, vt); err_sig1: diff --git a/tests/test_input.c b/tests/test_input.c index 8cf22943..532579b8 100644 --- a/tests/test_input.c +++ b/tests/test_input.c @@ -89,7 +89,7 @@ static void print_modifiers(unsigned int mods) } static void input_arrived(struct uterm_input *input, - struct uterm_input_event *ev, + struct uterm_input_key_event *ev, void *data) { char s[32]; @@ -148,12 +148,12 @@ static void monitor_event(struct uterm_monitor *mon, 0, 0, log_llog, NULL); if (ret) return; - ret = uterm_input_register_cb(input, input_arrived, NULL); + ret = uterm_input_register_key_cb(input, input_arrived, NULL); if (ret) return; uterm_input_wake_up(input); } else if (ev->type == UTERM_MONITOR_FREE_SEAT) { - uterm_input_unregister_cb(input, input_arrived, NULL); + uterm_input_unregister_key_cb(input, input_arrived, NULL); uterm_input_unref(input); } else if (ev->type == UTERM_MONITOR_NEW_DEV) { if (ev->dev_type == UTERM_MONITOR_INPUT) From db626a3fc8a391641f42cfba293fd8ebd43c6902 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 31 Oct 2025 11:11:09 +0100 Subject: [PATCH 04/17] gltex: use variable to avoid some code duplication. In gltex_draw(), add gl_x1, gl_x2, gl_y1, gl_y2, to store vertex coordinate. This is cosmetic and should have no impacts. Signed-off-by: Jocelyn Falempe --- src/text_gltex.c | 73 ++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/src/text_gltex.c b/src/text_gltex.c index 6a1c8f39..96b3d49e 100644 --- a/src/text_gltex.c +++ b/src/text_gltex.c @@ -602,6 +602,7 @@ static int gltex_draw(struct kmscon_text *txt, struct gltex *gt = txt->data; struct atlas *atlas; struct glyph *glyph; + float gl_x1, gl_x2, gl_y1, gl_y2; int ret, i, idx; if (!width) @@ -618,45 +619,39 @@ static int gltex_draw(struct kmscon_text *txt, if (atlas->cache_num >= atlas->cache_size) return -ERANGE; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 0] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 1] = - 1 - gt->advance_y * posy; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 2] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 3] = - 1 - (gt->advance_y * posy + gt->advance_y); - atlas->cache_pos[atlas->cache_num * 2 * 6 + 4] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 5] = - 1 - (gt->advance_y * posy + gt->advance_y); - - atlas->cache_pos[atlas->cache_num * 2 * 6 + 6] = - gt->advance_x * posx - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 7] = - 1 - gt->advance_y * posy; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 8] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 9] = - 1 - (gt->advance_y * posy + gt->advance_y); - atlas->cache_pos[atlas->cache_num * 2 * 6 + 10] = - gt->advance_x * posx + width * gt->advance_x - 1; - atlas->cache_pos[atlas->cache_num * 2 * 6 + 11] = - 1 - gt->advance_y * posy; - - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 0] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 1] = 0.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 2] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 3] = 1.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 4] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 5] = 1.0; - - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 6] = glyph->texoff; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 7] = 0.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 8] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 9] = 1.0; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 10] = glyph->texoff + width; - atlas->cache_texpos[atlas->cache_num * 2 * 6 + 11] = 0.0; + idx = atlas->cache_num * 2 * 6; + gl_x1 = gt->advance_x * posx - 1.0; + gl_x2 = gl_x1 + width * gt->advance_x; + gl_y1 = 1.0 - gt->advance_y * posy; + gl_y2 = gl_y1 - gt->advance_y; + + atlas->cache_pos[idx + 0] = gl_x1; + atlas->cache_pos[idx + 1] = gl_y1; + atlas->cache_pos[idx + 2] = gl_x1; + atlas->cache_pos[idx + 3] = gl_y2; + atlas->cache_pos[idx + 4] = gl_x2; + atlas->cache_pos[idx + 5] = gl_y2; + + atlas->cache_pos[idx + 6] = gl_x1; + atlas->cache_pos[idx + 7] = gl_y1; + atlas->cache_pos[idx + 8] = gl_x2; + atlas->cache_pos[idx + 9] = gl_y2; + atlas->cache_pos[idx + 10] = gl_x2; + atlas->cache_pos[idx + 11] = gl_y1; + + atlas->cache_texpos[idx + 0] = glyph->texoff; + atlas->cache_texpos[idx + 1] = 0.0; + atlas->cache_texpos[idx + 2] = glyph->texoff; + atlas->cache_texpos[idx + 3] = 1.0; + atlas->cache_texpos[idx + 4] = glyph->texoff + width; + atlas->cache_texpos[idx + 5] = 1.0; + + atlas->cache_texpos[idx + 6] = glyph->texoff; + atlas->cache_texpos[idx + 7] = 0.0; + atlas->cache_texpos[idx + 8] = glyph->texoff + width; + atlas->cache_texpos[idx + 9] = 1.0; + atlas->cache_texpos[idx + 10] = glyph->texoff + width; + atlas->cache_texpos[idx + 11] = 0.0; for (i = 0; i < 6; ++i) { idx = atlas->cache_num * 3 * 6 + i * 3; From 484231f8cc22961cc8f48f432ae0f72d0e2a5846 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 31 Oct 2025 10:43:05 +0100 Subject: [PATCH 05/17] Add a draw_pointer callback for the renderer This allows to draw a mouse pointer at specific location Signed-off-by: Jocelyn Falempe --- src/text.c | 24 ++++++++++++++++++++++++ src/text.h | 7 +++++++ 2 files changed, 31 insertions(+) diff --git a/src/text.c b/src/text.c index 6d1b496d..29cb079d 100644 --- a/src/text.c +++ b/src/text.c @@ -466,6 +466,30 @@ int kmscon_text_draw(struct kmscon_text *txt, return txt->ops->draw(txt, id, ch, len, width, posx, posy, attr); } +/** + * kmscon_text_draw_pointer: + * @txt: valid text renderer + * @x: X-position of the center of the pointer in pixel + * @y: Y-position of the center of the pointer in pixel + * @attr: glyph attributes + * + * This draws a single I glyph at the requested position. The position is a + * a pixel position! You must precede this call with kmscon_text_prepare(). + * Use this function to feed the mouse pointer into the rendering pipeline + * and finally call kmscon_text_render(). + * + * Returns: 0 on success or negative error code if it couldn't be drawn. + */ +int kmscon_text_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr) +{ + if (!txt || !txt->rendering || !txt->ops->draw_pointer) + return -EINVAL; + + return txt->ops->draw_pointer(txt, x, y, attr); +} + /** * kmscon_text_render: * @txt: valid text renderer diff --git a/src/text.h b/src/text.h index 0c51ce94..4fde896b 100644 --- a/src/text.h +++ b/src/text.h @@ -82,6 +82,10 @@ struct kmscon_text_ops { unsigned int width, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); + int (*draw_pointer) (struct kmscon_text *txt, + unsigned int x, + unsigned int y, + const struct tsm_screen_attr *attr); int (*render) (struct kmscon_text *txt); void (*abort) (struct kmscon_text *txt); }; @@ -113,6 +117,9 @@ int kmscon_text_draw(struct kmscon_text *txt, unsigned int width, unsigned int posx, unsigned int posy, const struct tsm_screen_attr *attr); +int kmscon_text_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr); int kmscon_text_render(struct kmscon_text *txt); void kmscon_text_abort(struct kmscon_text *txt); From e86704bdcdfcc66aac52127d1a5eededeb578d71 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 4 Nov 2025 23:39:08 +0100 Subject: [PATCH 06/17] Add draw_pointer to gltex renderer Signed-off-by: Jocelyn Falempe --- src/text_gltex.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/src/text_gltex.c b/src/text_gltex.c index 96b3d49e..4997514e 100644 --- a/src/text_gltex.c +++ b/src/text_gltex.c @@ -344,7 +344,7 @@ static struct atlas *get_atlas(struct kmscon_text *txt, unsigned int num) log_debug("new atlas of size %ux%u for %zu", width, height, newsize); - nsize = txt->cols * txt->rows; + nsize = txt->cols * txt->rows + 1; // +1 for the mouse pointer atlas->cache_pos = malloc(sizeof(GLfloat) * nsize * 2 * 6); if (!atlas->cache_pos) @@ -677,6 +677,92 @@ static int gltex_draw(struct kmscon_text *txt, return 0; } +static int gltex_draw_pointer(struct kmscon_text *txt, + unsigned int x, unsigned int y, + const struct tsm_screen_attr *attr) +{ + struct gltex *gt = txt->data; + struct atlas *atlas; + struct glyph *glyph; + float gl_x1, gl_x2, gl_y1, gl_y2; + unsigned int sw, sh; + int ret, i, idx; + uint32_t ch = 'I'; + uint64_t id = ch; + + ret = find_glyph(txt, &glyph, id, &ch, 1, attr); + if (ret) + return ret; + + atlas = glyph->atlas; + + if (atlas->cache_num >= atlas->cache_size) + return -ERANGE; + + if(txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + sw = gt->sw; + sh = gt->sh; + } else { + sw = gt->sh; + sh = gt->sw; + } + + if (x > sw) + x = sw; + + if (y > sh) + y = sh; + + gl_x1 = x * 2.0 / sw - 1.0 - gt->advance_x / 2.0; + gl_y1 = 1.0 - y * 2.0 / sh + gt->advance_y / 2.0; + gl_x2 = gl_x1 + gt->advance_x; + gl_y2 = gl_y1 - gt->advance_y; + + idx = atlas->cache_num * 2 * 6; + + atlas->cache_pos[idx + 0] = gl_x1; + atlas->cache_pos[idx + 1] = gl_y1; + atlas->cache_pos[idx + 2] = gl_x1; + atlas->cache_pos[idx + 3] = gl_y2; + atlas->cache_pos[idx + 4] = gl_x2; + atlas->cache_pos[idx + 5] = gl_y2; + + atlas->cache_pos[idx + 6] = gl_x1; + atlas->cache_pos[idx + 7] = gl_y1; + atlas->cache_pos[idx + 8] = gl_x2; + atlas->cache_pos[idx + 9] = gl_y2; + atlas->cache_pos[idx + 10] = gl_x2; + atlas->cache_pos[idx + 11] = gl_y1; + + atlas->cache_texpos[idx + 0] = glyph->texoff; + atlas->cache_texpos[idx + 1] = 0.0; + atlas->cache_texpos[idx + 2] = glyph->texoff; + atlas->cache_texpos[idx + 3] = 1.0; + atlas->cache_texpos[idx + 4] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 5] = 1.0; + + atlas->cache_texpos[idx + 6] = glyph->texoff; + atlas->cache_texpos[idx + 7] = 0.0; + atlas->cache_texpos[idx + 8] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 9] = 1.0; + atlas->cache_texpos[idx + 10] = glyph->texoff + 1.0; + atlas->cache_texpos[idx + 11] = 0.0; + + for (i = 0; i < 6; ++i) { + idx = atlas->cache_num * 3 * 6 + i * 3; + atlas->cache_fgcol[idx + 0] = attr->fr / 255.0; + atlas->cache_fgcol[idx + 1] = attr->fg / 255.0; + atlas->cache_fgcol[idx + 2] = attr->fb / 255.0; + atlas->cache_bgcol[idx + 0] = attr->br / 255.0; + atlas->cache_bgcol[idx + 1] = attr->bg / 255.0; + atlas->cache_bgcol[idx + 2] = attr->bb / 255.0; + } + + ++atlas->cache_num; + + return 0; +} + static int gltex_render(struct kmscon_text *txt) { struct gltex *gt = txt->data; @@ -743,6 +829,7 @@ struct kmscon_text_ops kmscon_text_gltex_ops = { .rotate = gltex_rotate, .prepare = gltex_prepare, .draw = gltex_draw, + .draw_pointer = gltex_draw_pointer, .render = gltex_render, .abort = NULL, }; From a3242780a94b47ac52eb81d18f3864116f8a8520 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Thu, 6 Nov 2025 23:21:42 +0100 Subject: [PATCH 07/17] Add draw_pointer to bblit renderer --- src/text_bblit.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/text_bblit.c b/src/text_bblit.c index 0c1a3240..1d6dcc3a 100644 --- a/src/text_bblit.c +++ b/src/text_bblit.c @@ -38,6 +38,7 @@ #include #include "font_rotate.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -242,6 +243,73 @@ static int bblit_draw(struct kmscon_text *txt, return ret; } +static int bblit_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct uterm_video_buffer *bb_glyph; + struct uterm_mode *mode; + uint32_t ch = 'I'; + uint64_t id = ch; + unsigned int sw, sh; + unsigned int m_x, m_y, x, y; + int ret; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + m_x = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + } else { + m_x = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + } + + ret = find_glyph(txt, &bb_glyph, id, &ch, 1, attr); + if (ret) + return ret; + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + /* draw glyph */ + ret = uterm_display_fake_blend(txt->disp, bb_glyph, x, y, + attr->fr, attr->fg, attr->fb, + attr->br, attr->bg, attr->bb); + return ret; +} + struct kmscon_text_ops kmscon_text_bblit_ops = { .name = "bblit", .owner = NULL, @@ -252,6 +320,7 @@ struct kmscon_text_ops kmscon_text_bblit_ops = { .rotate = bblit_rotate, .prepare = NULL, .draw = bblit_draw, + .draw_pointer = bblit_draw_pointer, .render = NULL, .abort = NULL, }; From 6cd25248ba44e6248aa62af6494724c2e81e4bf5 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Thu, 6 Nov 2025 23:53:08 +0100 Subject: [PATCH 08/17] Add draw_pointer to bbulk renderer --- src/text_bbulk.c | 98 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/src/text_bbulk.c b/src/text_bbulk.c index f22d2dc3..ae2cdca4 100644 --- a/src/text_bbulk.c +++ b/src/text_bbulk.c @@ -32,7 +32,6 @@ * pushes all of them at once to the video device. */ -#include #include #include #include @@ -41,6 +40,7 @@ #include "font_rotate.h" #include "shl_hashtable.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -48,6 +48,8 @@ struct bbulk { struct uterm_video_blend_req *reqs; + unsigned int req_len; + unsigned int req_total_len; struct shl_hashtable *glyphs; struct shl_hashtable *bold_glyphs; }; @@ -101,10 +103,12 @@ static int bbulk_set(struct kmscon_text *txt) txt->rows = sw / FONT_HEIGHT(txt); txt->cols = sh / FONT_WIDTH(txt); } - bb->reqs = malloc(sizeof(*bb->reqs) * txt->cols * txt->rows); + + bb->req_total_len = txt->cols * txt->rows + 1; // + 1 for the pointer + bb->reqs = malloc(sizeof(*bb->reqs) * bb->req_total_len); if (!bb->reqs) return -ENOMEM; - memset(bb->reqs, 0, sizeof(*bb->reqs) * txt->cols * txt->rows); + memset(bb->reqs, 0, sizeof(*bb->reqs) * bb->req_total_len); for (i = 0; i < txt->rows; ++i) { for (j = 0; j < txt->cols; ++j) { @@ -277,13 +281,94 @@ static int bbulk_draw(struct kmscon_text *txt, return 0; } +static int bblit_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct bbulk *bb = txt->data; + struct uterm_video_blend_req *req; + struct uterm_video_buffer *bb_glyph; + struct uterm_mode *mode; + uint32_t ch = 'I'; + uint64_t id = ch; + unsigned int sw, sh; + unsigned int m_x, m_y, x, y; + int ret; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + m_x = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + } else { + m_x = SHL_DIV_ROUND_UP(FONT_HEIGHT(txt), 2); + m_y = SHL_DIV_ROUND_UP(FONT_WIDTH(txt), 2); + } + + // pointer is the last request + req = &bb->reqs[bb->req_total_len - 1]; + + ret = find_glyph(txt, &bb_glyph, id, &ch, 1, attr); + if (ret) + return ret; + + req->buf = bb_glyph; + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + req->x = x; + req->y = y; + + req->fr = attr->fr; + req->fg = attr->fg; + req->fb = attr->fb; + req->br = attr->br; + req->bg = attr->bg; + req->bb = attr->bb; + + bb->req_len = bb->req_total_len; + return 0; +} + static int bbulk_render(struct kmscon_text *txt) { struct bbulk *bb = txt->data; int ret; ret = uterm_display_fake_blendv(txt->disp, bb->reqs, - txt->cols * txt->rows); + bb->req_len); return ret; } @@ -293,9 +378,9 @@ static int bbulk_prepare(struct kmscon_text *txt) int i; // Clear previous requests - for (i = 0; i < txt->rows * txt->cols; ++i) + for (i = 0; i < bb->req_total_len; ++i) bb->reqs[i].buf = NULL; - + bb->req_len = txt->cols * txt->rows; return 0; } @@ -309,6 +394,7 @@ struct kmscon_text_ops kmscon_text_bbulk_ops = { .rotate = bbulk_rotate, .prepare = bbulk_prepare, .draw = bbulk_draw, + .draw_pointer = bblit_draw_pointer, .render = bbulk_render, .abort = NULL, }; From 17f0c52ee488af4fa671a9af16f245737162ba6b Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 7 Nov 2025 16:12:49 +0100 Subject: [PATCH 09/17] Add draw_pointer to pixman renderer --- src/text_pixman.c | 117 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/src/text_pixman.c b/src/text_pixman.c index 8f4da68c..5c29e11a 100644 --- a/src/text_pixman.c +++ b/src/text_pixman.c @@ -36,6 +36,7 @@ #include "font_rotate.h" #include "shl_hashtable.h" #include "shl_log.h" +#include "shl_misc.h" #include "text.h" #include "uterm_video.h" @@ -431,7 +432,7 @@ static int tp_draw(struct kmscon_text *txt, * image for each glyph here. * libc malloc() is pretty fast, but this still costs us a lot of * rendering performance. */ - if (!fc.red && !fc.green && !fc.blue) { + if (fc.red == 0xff00 && fc.green == 0xff00 && fc.blue == 0xff00) { col = tp->white; pixman_image_ref(col); } else { @@ -491,6 +492,119 @@ static int tp_draw(struct kmscon_text *txt, return 0; } +static int tp_draw_pointer(struct kmscon_text *txt, + unsigned int pointer_x, unsigned int pointer_y, + const struct tsm_screen_attr *attr) +{ + struct tp_pixman *tp = txt->data; + struct uterm_mode *mode; + struct tp_glyph *glyph; + uint32_t ch = 'I'; + uint64_t id = ch; + int ret; + unsigned int x, y, w, h, sw, sh; + unsigned int m_x, m_y; + uint32_t bc; + pixman_color_t fc; + pixman_image_t *col; + + mode = uterm_display_get_current(txt->disp); + if (!mode) + return -EINVAL; + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + + ret = find_glyph(txt, &glyph, id, &ch, 1, attr); + if (ret) + return ret; + + bc = (attr->br << 16) | (attr->bg << 8) | (attr->bb); + fc.red = attr->fr << 8; + fc.green = attr->fg << 8; + fc.blue = attr->fb << 8; + fc.alpha = 0xffff; + + /* TODO: We _really_ should fix pixman to allow something like + * pixman_image_set_solid_fill(img, &fc) to avoid allocating a pixman + * image for each glyph here. + * libc malloc() is pretty fast, but this still costs us a lot of + * rendering performance. */ + if (attr->fr == 0xff && attr->fg == 0xff && attr->fb == 0xff) { + col = tp->white; + pixman_image_ref(col); + } else { + col = pixman_image_create_solid_fill(&fc); + if (!col) { + log_error("cannot create pixman color image"); + return -ENOMEM; + } + } + + if (txt->orientation == OR_NORMAL || txt->orientation == OR_UPSIDE_DOWN) { + w = FONT_WIDTH(txt); + h = FONT_HEIGHT(txt); + } else { + w = FONT_HEIGHT(txt); + h = FONT_WIDTH(txt); + } + + m_x = SHL_DIV_ROUND_UP(w, 2); + m_y = SHL_DIV_ROUND_UP(h, 2); + + switch (txt->orientation) { + default: + case OR_NORMAL: + x = pointer_x; + y = pointer_y; + break; + case OR_UPSIDE_DOWN: + x = sw - pointer_x; + y = sh - pointer_y; + break; + case OR_RIGHT: + x = sw - pointer_y; + y = pointer_x; + break; + case OR_LEFT: + x = pointer_y; + y = sh - pointer_x; + break; + } + if (x < m_x) + x = m_x; + if (x + m_x > sw) + x = sw - m_x; + if (y < m_y) + y = m_y; + if (y + m_y > sh) + y = sh - m_y; + x -= m_x; + y -= m_y; + + if (!bc) { + pixman_image_composite(PIXMAN_OP_SRC, + col, + glyph->surf, + tp->surf[tp->cur], + 0, 0, 0, 0, + x, y, w, h); + } else { + pixman_fill(tp->c_data, tp->c_stride / 4, tp->c_bpp, + x, y, w, h, bc); + + pixman_image_composite(PIXMAN_OP_OVER, + col, + glyph->surf, + tp->surf[tp->cur], + 0, 0, 0, 0, + x, y, w, h); + } + + pixman_image_unref(col); + + return 0; +} + static int tp_render(struct kmscon_text *txt) { struct tp_pixman *tp = txt->data; @@ -519,6 +633,7 @@ struct kmscon_text_ops kmscon_text_pixman_ops = { .rotate = tp_rotate, .prepare = tp_prepare, .draw = tp_draw, + .draw_pointer = tp_draw_pointer, .render = tp_render, .abort = NULL, }; From 37d6f0232318dfbfe67f23d196f42c1cb8df03c9 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 28 Oct 2025 13:44:03 +0100 Subject: [PATCH 10/17] Add basic mouse support Start support for standard mouse, using relative EV_REL events Signed-off-by: Jocelyn Falempe --- src/kmscon_terminal.c | 166 ++++++++++++++++++++++++++++++++++++- src/meson.build | 1 + src/uterm_input.c | 94 +++++++++++++++++---- src/uterm_input.h | 23 +++++ src/uterm_input_internal.h | 27 ++++++ src/uterm_input_pointer.c | 75 +++++++++++++++++ 6 files changed, 368 insertions(+), 18 deletions(-) create mode 100644 src/uterm_input_pointer.c diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index d1b85eb9..3deda0dc 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -37,6 +37,7 @@ #include #include "conf.h" #include "eloop.h" +#include "font.h" #include "kmscon_conf.h" #include "kmscon_seat.h" #include "kmscon_terminal.h" @@ -59,6 +60,17 @@ struct screen { bool pending; }; +struct kmscon_pointer { + bool visible; + bool select; + int32_t x; + int32_t y; + unsigned int posx; + unsigned int posy; + char *copy; + int copy_len; +}; + struct kmscon_terminal { unsigned long ref; struct ev_eloop *eloop; @@ -82,6 +94,8 @@ struct kmscon_terminal { struct kmscon_font_attr font_attr; struct kmscon_font *font; struct kmscon_font *bold_font; + + struct kmscon_pointer pointer; }; static void do_clear_margins(struct screen *scr) @@ -130,6 +144,35 @@ static void do_clear_margins(struct screen *scr) static int font_set(struct kmscon_terminal *term); + +static void coord_to_cell(struct kmscon_terminal *term, int32_t x, int32_t y, unsigned int *posx, unsigned int *posy) +{ + int fw = term->font->attr.width; + int fh = term->font->attr.height; + int w = tsm_screen_get_width(term->console); + int h = tsm_screen_get_height(term->console); + + *posx = x / fw; + *posy = y / fh; + + if (*posx >= w) + *posx = w; + + if (*posy >= h) + *posy = h; +} + +static void draw_pointer(struct screen *scr) +{ + struct tsm_screen_attr attr; + + if (!scr->term->pointer.visible) + return; + + tsm_vte_get_def_attr(scr->term->vte, &attr); + kmscon_text_draw_pointer(scr->txt, scr->term->pointer.x, scr->term->pointer.y, &attr); +} + static void do_redraw_screen(struct screen *scr) { int ret; @@ -142,6 +185,7 @@ static void do_redraw_screen(struct screen *scr) kmscon_text_prepare(scr->txt); tsm_screen_draw(scr->term->console, kmscon_text_draw_cb, scr->txt); + draw_pointer(scr); kmscon_text_render(scr->txt); ret = uterm_display_swap(scr->disp, false); @@ -245,6 +289,15 @@ static void osc_event(struct tsm_vte *vte, const char *osc_string, } } +static void mouse_event(struct tsm_vte *vte, enum tsm_mouse_track_mode track_mode, + bool track_pixels, void *data) +{ + struct kmscon_terminal *term = data; + + term->pointer.select = false; + tsm_screen_selection_reset(term->console); +} + /* * Resize terminal * We support multiple monitors per terminal. As some software-rendering @@ -585,6 +638,109 @@ static void input_event(struct uterm_input *input, } } +static void start_selection(struct tsm_screen *console, unsigned int x, unsigned int y) +{ + tsm_screen_selection_reset(console); + tsm_screen_selection_start(console, x, y); +} + +static void update_selection(struct tsm_screen *console, unsigned int x, unsigned int y) +{ + tsm_screen_selection_target(console, x, y); +} + +static void copy_selection(struct kmscon_terminal *term) +{ + if (term->pointer.copy) { + free(term->pointer.copy); + term->pointer.copy = NULL; + term->pointer.copy_len = 0; + } + term->pointer.copy_len = tsm_screen_selection_copy(term->console, &term->pointer.copy); +} + +static void forward_pointer_event(struct kmscon_terminal *term, struct uterm_input_pointer_event *ev) +{ + unsigned int event; + + switch (ev->event) { + case UTERM_MOVED: + event = TSM_MOUSE_EVENT_MOVED; + break; + case UTERM_BUTTON: + if (ev->pressed) + event = TSM_MOUSE_EVENT_PRESSED; + else + event = TSM_MOUSE_EVENT_RELEASED; + break; + default: + return; + } + tsm_vte_handle_mouse(term->vte, term->pointer.posx, term->pointer.posy, + term->pointer.x, term->pointer.y, ev->button, event, 0); +} + +static void handle_pointer_button(struct kmscon_terminal *term, struct uterm_input_pointer_event *ev) +{ + switch(ev->button) { + case 0: + if (ev->pressed) { + term->pointer.select = true; + start_selection(term->console, term->pointer.posx, term->pointer.posy); + } else { + if (term->pointer.select) + copy_selection(term); + term->pointer.select = false; + } + break; + case 1: + term->pointer.select = false; + tsm_screen_selection_reset(term->console); + break; + case 2: + if (ev->pressed) { + kmscon_pty_write(term->pty, term->pointer.copy, term->pointer.copy_len); + tsm_screen_selection_reset(term->console); + } + } +} + +static void pointer_event(struct uterm_input *input, + struct uterm_input_pointer_event *ev, + void *data) +{ + struct kmscon_terminal *term = data; + + if (ev->event == UTERM_MOVED) { + term->pointer.x = ev->pointer_x; + term->pointer.y = ev->pointer_y; + + coord_to_cell(term, term->pointer.x, term->pointer.y, &term->pointer.posx, &term->pointer.posy); + term->pointer.visible = true; + } + + if (tsm_vte_get_mouse_mode(term->vte) != TSM_MOUSE_TRACK_DISABLE && + ev->event != UTERM_SYNC) { + forward_pointer_event(term, ev); + return; + } + + switch (ev->event) { + default: + break; + case UTERM_MOVED: + if (term->pointer.select) + update_selection(term->console,term->pointer.posx, term->pointer.posy); + break; + case UTERM_BUTTON: + handle_pointer_button(term, ev); + break; + case UTERM_SYNC: + redraw_all(term); + break; + } +} + static void rm_all_screens(struct kmscon_terminal *term) { struct shl_dlist *iter; @@ -631,6 +787,7 @@ static void terminal_destroy(struct kmscon_terminal *term) terminal_close(term); rm_all_screens(term); + uterm_input_unregister_pointer_cb(term->input, pointer_event, term); uterm_input_unregister_key_cb(term->input, input_event, term); ev_eloop_rm_fd(term->ptyfd); kmscon_pty_unref(term->pty); @@ -745,6 +902,7 @@ int kmscon_terminal_register(struct kmscon_session **out, BUILD_BACKSPACE_SENDS_DELETE); tsm_vte_set_osc_cb(term->vte, osc_event, (void *)term); + tsm_vte_set_mouse_cb(term->vte, mouse_event, (void *)term); ret = tsm_vte_set_palette(term->vte, term->conf->palette); if (ret) @@ -796,11 +954,15 @@ int kmscon_terminal_register(struct kmscon_session **out, if (ret) goto err_ptyfd; + ret = uterm_input_register_pointer_cb(term->input, pointer_event, term); + if (ret) + goto err_input; + ret = kmscon_seat_register_session(seat, &term->session, session_event, term); if (ret) { log_error("cannot register session for terminal: %d", ret); - goto err_input; + goto err_pointer; } ev_eloop_ref(term->eloop); @@ -809,6 +971,8 @@ int kmscon_terminal_register(struct kmscon_session **out, log_debug("new terminal object %p", term); return 0; +err_pointer: + uterm_input_unregister_pointer_cb(term->input, pointer_event, term); err_input: uterm_input_unregister_key_cb(term->input, input_event, term); err_ptyfd: diff --git a/src/meson.build b/src/meson.build index dee686c3..3f6db43f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -103,6 +103,7 @@ uterm_srcs = [ 'uterm_vt.c', 'uterm_input.c', 'uterm_input_uxkb.c', + 'uterm_input_pointer.c', embed_gen.process('uterm_input_fallback.xkb', extra_args: keymap_regex), ] uterm_dep = [ diff --git a/src/uterm_input.c b/src/uterm_input.c index 41061e0e..18c33eca 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -51,15 +51,24 @@ static void input_free_dev(struct uterm_input_dev *dev); -static void notify_key(struct uterm_input_dev *dev, - uint16_t type, - uint16_t code, - int32_t value) +static void notify_event(struct uterm_input_dev *dev, + uint16_t type, + uint16_t code, + int32_t value) { - if (type != EV_KEY) - return; - - uxkb_dev_process(dev, value, code); + switch (type) { + case EV_KEY: + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_process(dev, value, code); + pointer_dev_button(dev, code, value); + break; + case EV_REL: + pointer_dev_rel(dev, code, value); + break; + case EV_SYN: + pointer_dev_sync(dev); + break; + } } static void input_data_dev(struct ev_fd *fd, int mask, void *data) @@ -93,7 +102,7 @@ static void input_data_dev(struct ev_fd *fd, int mask, void *data) } else { n = len / sizeof(*ev); for (i = 0; i < n; i++) - notify_key(dev, ev[i].type, ev[i].code, + notify_event(dev, ev[i].type, ev[i].code, ev[i].value); } } @@ -112,8 +121,8 @@ static int input_wake_up_dev(struct uterm_input_dev *dev) dev->node, errno); return -EFAULT; } - - uxkb_dev_wake_up(dev); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_wake_up(dev); ret = ev_eloop_new_fd(dev->input->eloop, &dev->fd, dev->rfd, EV_READABLE, input_data_dev, dev); @@ -131,7 +140,8 @@ static void input_sleep_dev(struct uterm_input_dev *dev) if (dev->rfd < 0) return; - uxkb_dev_sleep(dev); + if (dev->capabilities & UTERM_DEVICE_HAS_KEYS) + uxkb_dev_sleep(dev); dev->repeating = false; ev_timer_update(dev->repeat_timer, NULL); @@ -197,6 +207,7 @@ static void input_new_dev(struct uterm_input *input, dev->input = input; dev->rfd = -1; dev->capabilities = capabilities; + dev->pointer.kind = POINTER_NONE; dev->node = strdup(node); if (!dev->node) @@ -207,6 +218,8 @@ static void input_new_dev(struct uterm_input *input, if (ret) goto err_node; } + if (dev->capabilities & UTERM_DEVICE_HAS_REL) + dev->pointer.kind = POINTER_MOUSE; if (input->awake > 0) { ret = input_wake_up_dev(dev); @@ -285,6 +298,10 @@ int uterm_input_new(struct uterm_input **out, if (ret) goto err_free; + ret = shl_hook_new(&input->pointer_hook); + if (ret) + goto err_hook; + /* xkbcommon won't use the XKB_DEFAULT_OPTIONS environment * variable if options is an empty string. * So if all variables are empty, use NULL instead. @@ -300,13 +317,16 @@ int uterm_input_new(struct uterm_input **out, ret = uxkb_desc_init(input, model, layout, variant, options, locale, keymap, compose_file, compose_file_len); if (ret) - goto err_hook; + goto err_hook_pointer; llog_debug(input, "new object %p", input); ev_eloop_ref(input->eloop); *out = input; return 0; +err_hook_pointer: + shl_hook_free(input->pointer_hook); + err_hook: shl_hook_free(input->key_hook); @@ -359,6 +379,7 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, unsigned int capabilities = 0; unsigned long evbits[NLONGS(EV_CNT)] = { 0 }; unsigned long keybits[NLONGS(KEY_CNT)] = { 0 }; + unsigned long relbits[NLONGS(REL_CNT)] = {0}; fd = open(node, O_NONBLOCK | O_CLOEXEC | O_RDONLY); if (fd < 0) @@ -386,6 +407,18 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, break; } } + if (input_bit_is_set(keybits, BTN_LEFT)) + capabilities |= UTERM_DEVICE_HAS_MOUSE_BTN; + } + + if (input_bit_is_set(evbits, EV_SYN) && + input_bit_is_set(evbits, EV_REL)) { + ret = ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits); + if (ret < 0) + goto err_ioctl; + if (input_bit_is_set(relbits, REL_X) && + input_bit_is_set(relbits, REL_Y)) + capabilities |= UTERM_DEVICE_HAS_REL; } if (input_bit_is_set(evbits, EV_LED)) @@ -401,6 +434,8 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, return 0; } +#define HAS_ALL(caps, flags) (((caps) & (flags)) == (flags)) + SHL_EXPORT void uterm_input_add_dev(struct uterm_input *input, const char *node) { @@ -410,12 +445,15 @@ void uterm_input_add_dev(struct uterm_input *input, const char *node) return; capabilities = probe_device_capabilities(input, node); - if (!(capabilities & UTERM_DEVICE_HAS_KEYS)) { - llog_debug(input, "ignoring non-useful device %s", node); + if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_KEYS)) { + input_new_dev(input, node, capabilities); return; } - - input_new_dev(input, node, capabilities); + if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_REL | UTERM_DEVICE_HAS_MOUSE_BTN)) { + input_new_dev(input, node, capabilities); + } else { + llog_debug(input, "ignoring non-useful device %s", node); + } } SHL_EXPORT @@ -460,6 +498,28 @@ void uterm_input_unregister_key_cb(struct uterm_input *input, shl_hook_rm_cast(input->key_hook, cb, data); } +SHL_EXPORT +int uterm_input_register_pointer_cb(struct uterm_input *input, + uterm_input_pointer_cb cb, + void *data) +{ + if (!input || !cb) + return -EINVAL; + + return shl_hook_add_cast(input->pointer_hook, cb, data, false); +} + +SHL_EXPORT +void uterm_input_unregister_pointer_cb(struct uterm_input *input, + uterm_input_pointer_cb cb, + void *data) +{ + if (!input || !cb) + return; + + shl_hook_rm_cast(input->pointer_hook, cb, data); +} + SHL_EXPORT void uterm_input_sleep(struct uterm_input *input) { diff --git a/src/uterm_input.h b/src/uterm_input.h index 7eadca75..cdbeff46 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -72,12 +72,30 @@ struct uterm_input_key_event { uint32_t *codepoints; /* ucs4 unicode value or UTERM_INPUT_INVALID */ }; +enum uterm_input_pointer_type { + UTERM_MOVED, + UTERM_BUTTON, + UTERM_SYNC, +}; + +struct uterm_input_pointer_event { + enum uterm_input_pointer_type event; + int32_t pointer_x; + int32_t pointer_y; + uint8_t button; + bool pressed; +}; + #define UTERM_INPUT_HAS_MODS(_ev, _mods) (((_ev)->mods & (_mods)) == (_mods)) typedef void (*uterm_input_key_cb) (struct uterm_input *input, struct uterm_input_key_event *ev, void *data); +typedef void (*uterm_input_pointer_cb) (struct uterm_input *input, + struct uterm_input_pointer_event *ev, + void *data); + int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, const char *model, const char *layout, const char *variant, const char *options, const char *locale, const char *keymap, @@ -95,6 +113,11 @@ int uterm_input_register_key_cb(struct uterm_input *input, uterm_input_key_cb cb void uterm_input_unregister_key_cb(struct uterm_input *input, uterm_input_key_cb cb, void *data); +int uterm_input_register_pointer_cb(struct uterm_input *input, uterm_input_pointer_cb cb, + void *data); +void uterm_input_unregister_pointer_cb(struct uterm_input *input, uterm_input_pointer_cb cb, + void *data); + void uterm_input_sleep(struct uterm_input *input); void uterm_input_wake_up(struct uterm_input *input); bool uterm_input_is_awake(struct uterm_input *input); diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index ae71be01..1506b4fa 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -42,6 +42,21 @@ enum uterm_input_device_capability { UTERM_DEVICE_HAS_KEYS = (1 << 0), UTERM_DEVICE_HAS_LEDS = (1 << 1), + UTERM_DEVICE_HAS_REL = (1 << 2), + UTERM_DEVICE_HAS_MOUSE_BTN = (1 << 4), +}; + +enum pointer_kind { + POINTER_NONE, + POINTER_MOUSE, +}; + +struct uterm_input_pointer { + /* For pointers (mouse/trackpad/trackpoint/touchscreen) */ + enum pointer_kind kind; + enum uterm_input_pointer_type action; + int32_t x; + int32_t y; }; struct uterm_input_dev { @@ -52,6 +67,8 @@ struct uterm_input_dev { int rfd; char *node; struct ev_fd *fd; + + /* For keyboards */ struct xkb_state *state; struct xkb_compose_state *compose_state; /* Used in sleep/wake up to store the key's pressed/released state. */ @@ -63,6 +80,8 @@ struct uterm_input_dev { bool repeating; struct ev_timer *repeat_timer; + + struct uterm_input_pointer pointer; }; struct uterm_input { @@ -79,6 +98,8 @@ struct uterm_input { struct xkb_keymap *keymap; struct xkb_compose_table *compose_table; + struct shl_hook *pointer_hook; + struct shl_dlist devices; }; @@ -106,4 +127,10 @@ int uxkb_dev_process(struct uterm_input_dev *dev, void uxkb_dev_sleep(struct uterm_input_dev *dev); void uxkb_dev_wake_up(struct uterm_input_dev *dev); +void pointer_dev_rel(struct uterm_input_dev *dev, + uint16_t code, int32_t value); +void pointer_dev_button(struct uterm_input_dev *dev, + uint16_t code, int32_t value); +void pointer_dev_sync(struct uterm_input_dev *dev); + #endif /* UTERM_INPUT_INTERNAL_H */ diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c new file mode 100644 index 00000000..79746b98 --- /dev/null +++ b/src/uterm_input_pointer.c @@ -0,0 +1,75 @@ +#include +#include "shl_hook.h" +#include "shl_llog.h" +#include "uterm_input.h" +#include "uterm_input_internal.h" + + +static void pointer_dev_send_move(struct uterm_input_dev *dev) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_MOVED; + pev.pointer_x = dev->pointer.x; + pev.pointer_y = dev->pointer.y; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +static void pointer_dev_send_button(struct uterm_input_dev *dev, uint8_t button, bool pressed) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_BUTTON; + pev.button = button; + pev.pressed = pressed; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +void pointer_dev_sync(struct uterm_input_dev *dev) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_SYNC; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + +void pointer_dev_rel(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + switch (code) { + case REL_X: + dev->pointer.x += value; + pointer_dev_send_move(dev); + break; + case REL_Y: + dev->pointer.y += value; + pointer_dev_send_move(dev); + break; + default: + break; + } +} + +void pointer_dev_button(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + bool pressed = (value == 1); + + switch (code) { + case BTN_LEFT: + pointer_dev_send_button(dev, 0, pressed); + break; + case BTN_RIGHT: + pointer_dev_send_button(dev, 1, pressed); + break; + case BTN_MIDDLE: + pointer_dev_send_button(dev, 2, pressed); + break; + default: + break; + } +} + From e8eb51ee98fe0ff3d07aecc53ecccf5f86dfb33d Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 31 Oct 2025 17:30:53 +0100 Subject: [PATCH 11/17] Make sure the mouse pointer won't go out of the screen input_pointer needs to know the screen size in pixel, and make the pointer stay within the screen boundaries. Signed-off-by: Jocelyn Falempe --- src/kmscon_terminal.c | 44 ++++++++++++++++++++++++++++++++++++++ src/uterm_input.c | 12 +++++++++++ src/uterm_input.h | 3 +++ src/uterm_input_internal.h | 2 ++ src/uterm_input_pointer.c | 8 +++++++ 5 files changed, 69 insertions(+) diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 3deda0dc..b645ccce 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -245,6 +245,46 @@ static bool has_kms_display(struct kmscon_terminal *term) return false; } +/* + * Align the pointer maximum to the minimum width and height of all screens + * according to their orientation, as kmscon only support mirroring, and one + * terminal size for all screens. + */ +static void update_pointer_max_all(struct kmscon_terminal *term) +{ + struct shl_dlist *iter; + struct screen *scr; + struct uterm_mode *mode; + unsigned int max_x = INT_MAX; + unsigned int max_y = INT_MAX; + unsigned int sw, sh; + + if (!term->awake) + return; + + shl_dlist_for_each(iter, &term->screens) { + scr = shl_dlist_entry(iter, struct screen, list); + + mode = uterm_display_get_current(scr->disp); + if (!mode) + continue; + + if (scr->txt->orientation == OR_NORMAL || scr->txt->orientation == OR_UPSIDE_DOWN) { + sw = uterm_mode_get_width(mode); + sh = uterm_mode_get_height(mode); + } else { + sw = uterm_mode_get_height(mode); + sh = uterm_mode_get_width(mode); + } + if (sw < max_x) + max_x = sw; + if (sh < max_y) + max_y = sh; + } + if (max_x < INT_MAX && max_y < INT_MAX) + uterm_input_set_pointer_max(term->input, max_x, max_y); +} + static void redraw_all_test(struct kmscon_terminal *term) { struct shl_dlist *iter; @@ -317,6 +357,8 @@ static void terminal_resize(struct kmscon_terminal *term, { bool resize = false; + update_pointer_max_all(term); + if (!term->min_cols || (cols > 0 && cols < term->min_cols)) { term->min_cols = cols; resize = true; @@ -771,6 +813,8 @@ static int terminal_open(struct kmscon_terminal *term) return ret; term->opened = true; + + update_pointer_max_all(term); redraw_all(term); return 0; } diff --git a/src/uterm_input.c b/src/uterm_input.c index 18c33eca..f70b92b3 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -577,3 +577,15 @@ bool uterm_input_is_awake(struct uterm_input *input) return input->awake > 0; } + +SHL_EXPORT +void uterm_input_set_pointer_max(struct uterm_input *input, + unsigned int max_x, + unsigned int max_y) +{ + if (!input) + return; + + input->pointer_max_x = max_x; + input->pointer_max_y = max_y; +} diff --git a/src/uterm_input.h b/src/uterm_input.h index cdbeff46..8f375e2f 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -121,5 +121,8 @@ void uterm_input_unregister_pointer_cb(struct uterm_input *input, uterm_input_po void uterm_input_sleep(struct uterm_input *input); void uterm_input_wake_up(struct uterm_input *input); bool uterm_input_is_awake(struct uterm_input *input); +void uterm_input_set_pointer_max(struct uterm_input *input, + unsigned int max_x, + unsigned int max_y); #endif /* UTERM_UTERM_INPUT_H */ diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 1506b4fa..cdb50e33 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -99,6 +99,8 @@ struct uterm_input { struct xkb_compose_table *compose_table; struct shl_hook *pointer_hook; + int32_t pointer_max_x; + int32_t pointer_max_y; struct shl_dlist devices; }; diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c index 79746b98..8cac57bd 100644 --- a/src/uterm_input_pointer.c +++ b/src/uterm_input_pointer.c @@ -42,10 +42,18 @@ void pointer_dev_rel(struct uterm_input_dev *dev, switch (code) { case REL_X: dev->pointer.x += value; + if (dev->pointer.x < 0) + dev->pointer.x = 0; + if (dev->pointer.x > dev->input->pointer_max_x) + dev->pointer.x = dev->input->pointer_max_x; pointer_dev_send_move(dev); break; case REL_Y: dev->pointer.y += value; + if (dev->pointer.y < 0) + dev->pointer.y = 0; + if (dev->pointer.y > dev->input->pointer_max_y) + dev->pointer.y = dev->input->pointer_max_y; pointer_dev_send_move(dev); break; default: From 36a7775f645eb1cb1e9471e3ad814be99f3be28a Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 3 Nov 2025 14:55:53 +0100 Subject: [PATCH 12/17] Add support for touchpad and virtual mouse Touchpad and mouse in VM, use absolute coordinate, with EV_ABS. Touchpad is a bit special because it's smaller than the screen, so you need to slide multiple time to go from one edge to the other. Handle double tap and triple tap as paste, to make it easy to copy/ paste with a touchpad. This is very basic, and doesn't include all the quirks from libinput, but I hope it works in most cases. Touchscreen may work, but I don't feel it's needed to support it in kmscon, as you will need gesture/onscreen keyboard to make it useful. --- src/uterm_input.c | 63 ++++++++++++++++++++++++++++++++++++-- src/uterm_input_internal.h | 17 +++++++++- src/uterm_input_pointer.c | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/uterm_input.c b/src/uterm_input.c index f70b92b3..1da25486 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -65,6 +65,9 @@ static void notify_event(struct uterm_input_dev *dev, case EV_REL: pointer_dev_rel(dev, code, value); break; + case EV_ABS: + pointer_dev_abs(dev, code, value); + break; case EV_SYN: pointer_dev_sync(dev); break; @@ -192,6 +195,38 @@ static void input_exit_keyboard(struct uterm_input_dev *dev) free(dev->event.keysyms); } +static int input_init_abs(struct uterm_input_dev *dev) +{ + struct input_absinfo info; + int ret, fd; + + fd = open(dev->node, O_NONBLOCK | O_CLOEXEC | O_RDONLY); + if (fd < 0) + return 0; + + ret = ioctl(fd, EVIOCGABS(ABS_X), &info); + if (ret < 0) + goto err_closefd; + + dev->pointer.min_x = info.minimum; + dev->pointer.max_x = info.maximum; + + ret = ioctl(fd, EVIOCGABS(ABS_Y), &info); + if (ret < 0) + goto err_closefd; + + dev->pointer.min_y = info.minimum; + dev->pointer.max_y = info.maximum; + + llog_debug(dev->input, "ABSX min %d max %d ABSY min %d max %d\n", + dev->pointer.min_x, dev->pointer.max_x, + dev->pointer.min_y, dev->pointer.max_y); + return 0; + +err_closefd: + close(fd); + return ret; +} static void input_new_dev(struct uterm_input *input, const char *node, @@ -218,6 +253,15 @@ static void input_new_dev(struct uterm_input *input, if (ret) goto err_node; } + if (dev->capabilities & UTERM_DEVICE_HAS_ABS) { + ret = input_init_abs(dev); + if (ret) + goto err_node; + if (dev->capabilities & UTERM_DEVICE_HAS_TOUCH) + dev->pointer.kind = POINTER_TOUCHPAD; + else + dev->pointer.kind = POINTER_VMOUSE; + } if (dev->capabilities & UTERM_DEVICE_HAS_REL) dev->pointer.kind = POINTER_MOUSE; @@ -379,7 +423,8 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, unsigned int capabilities = 0; unsigned long evbits[NLONGS(EV_CNT)] = { 0 }; unsigned long keybits[NLONGS(KEY_CNT)] = { 0 }; - unsigned long relbits[NLONGS(REL_CNT)] = {0}; + unsigned long relbits[NLONGS(REL_CNT)] = { 0 }; + unsigned long absbits[NLONGS(ABS_CNT)] = { 0 }; fd = open(node, O_NONBLOCK | O_CLOEXEC | O_RDONLY); if (fd < 0) @@ -409,6 +454,8 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, } if (input_bit_is_set(keybits, BTN_LEFT)) capabilities |= UTERM_DEVICE_HAS_MOUSE_BTN; + if (input_bit_is_set(keybits, BTN_TOUCH)) + capabilities |= UTERM_DEVICE_HAS_TOUCH; } if (input_bit_is_set(evbits, EV_SYN) && @@ -421,6 +468,16 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, capabilities |= UTERM_DEVICE_HAS_REL; } + if (input_bit_is_set(evbits, EV_SYN) && + input_bit_is_set(evbits, EV_ABS)) { + ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits); + if (ret < 0) + goto err_ioctl; + if (input_bit_is_set(absbits, ABS_X) && + input_bit_is_set(absbits, ABS_Y)) + capabilities |= UTERM_DEVICE_HAS_ABS; + } + if (input_bit_is_set(evbits, EV_LED)) capabilities |= UTERM_DEVICE_HAS_LEDS; @@ -449,7 +506,9 @@ void uterm_input_add_dev(struct uterm_input *input, const char *node) input_new_dev(input, node, capabilities); return; } - if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_REL | UTERM_DEVICE_HAS_MOUSE_BTN)) { + if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_REL | UTERM_DEVICE_HAS_MOUSE_BTN) || + HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_TOUCH) || + HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_MOUSE_BTN)) { input_new_dev(input, node, capabilities); } else { llog_debug(input, "ignoring non-useful device %s", node); diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index cdb50e33..f5de2466 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -43,12 +43,16 @@ enum uterm_input_device_capability { UTERM_DEVICE_HAS_KEYS = (1 << 0), UTERM_DEVICE_HAS_LEDS = (1 << 1), UTERM_DEVICE_HAS_REL = (1 << 2), + UTERM_DEVICE_HAS_ABS = (1 << 3), UTERM_DEVICE_HAS_MOUSE_BTN = (1 << 4), + UTERM_DEVICE_HAS_TOUCH = (1 << 5), }; enum pointer_kind { POINTER_NONE, POINTER_MOUSE, + POINTER_TOUCHPAD, + POINTER_VMOUSE, }; struct uterm_input_pointer { @@ -57,6 +61,15 @@ struct uterm_input_pointer { enum uterm_input_pointer_type action; int32_t x; int32_t y; + + bool touchpaddown; + int32_t off_x; + int32_t off_y; + + int32_t min_x; + int32_t max_x; + int32_t min_y; + int32_t max_y; }; struct uterm_input_dev { @@ -131,8 +144,10 @@ void uxkb_dev_wake_up(struct uterm_input_dev *dev); void pointer_dev_rel(struct uterm_input_dev *dev, uint16_t code, int32_t value); +void pointer_dev_abs(struct uterm_input_dev *dev, + uint16_t code, int32_t value); void pointer_dev_button(struct uterm_input_dev *dev, - uint16_t code, int32_t value); + uint16_t code, int32_t value); void pointer_dev_sync(struct uterm_input_dev *dev); #endif /* UTERM_INPUT_INTERNAL_H */ diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c index 8cac57bd..ef04a89a 100644 --- a/src/uterm_input_pointer.c +++ b/src/uterm_input_pointer.c @@ -1,3 +1,4 @@ +#include #include #include "shl_hook.h" #include "shl_llog.h" @@ -34,6 +35,7 @@ void pointer_dev_sync(struct uterm_input_dev *dev) pev.event = UTERM_SYNC; shl_hook_call(dev->input->pointer_hook, dev->input, &pev); + dev->pointer.touchpaddown = false; } void pointer_dev_rel(struct uterm_input_dev *dev, @@ -61,6 +63,56 @@ void pointer_dev_rel(struct uterm_input_dev *dev, } } +void pointer_dev_abs(struct uterm_input_dev *dev, + uint16_t code, int32_t value) +{ + switch (code) { + case ABS_X: + if (dev->pointer.kind == POINTER_TOUCHPAD) { + if (dev->pointer.touchpaddown == true) + dev->pointer.off_x = dev->pointer.x - value; + + dev->pointer.x = dev->pointer.off_x + value; + if (dev->pointer.x < 0) { + dev->pointer.x = 0; + dev->pointer.off_x = -value; + } + if (dev->pointer.x > dev->input->pointer_max_x) { + dev->pointer.x = dev->input->pointer_max_x; + dev->pointer.off_x = dev->input->pointer_max_x - value; + } + } else if (dev->pointer.kind == POINTER_VMOUSE) { + dev->pointer.x = ((value - dev->pointer.min_x) * dev->input->pointer_max_x) / (dev->pointer.max_x - dev->pointer.min_x); + } else { + return; + } + break; + case ABS_Y: + if (dev->pointer.kind == POINTER_TOUCHPAD) { + if (dev->pointer.touchpaddown == true) + dev->pointer.off_y = dev->pointer.y - value; + + dev->pointer.y = dev->pointer.off_y + value; + if (dev->pointer.y < 0) { + dev->pointer.y = 0; + dev->pointer.off_y = -value; + } + if (dev->pointer.y > dev->input->pointer_max_y) { + dev->pointer.y = dev->input->pointer_max_y; + dev->pointer.off_y = dev->input->pointer_max_y - value; + } + } else if (dev->pointer.kind == POINTER_VMOUSE) { + dev->pointer.y = ((value - dev->pointer.min_y) * dev->input->pointer_max_y) / (dev->pointer.max_y - dev->pointer.min_y); + } else { + return; + } + break; + default: + return; + } + pointer_dev_send_move(dev); +} + void pointer_dev_button(struct uterm_input_dev *dev, uint16_t code, int32_t value) { @@ -73,9 +125,14 @@ void pointer_dev_button(struct uterm_input_dev *dev, case BTN_RIGHT: pointer_dev_send_button(dev, 1, pressed); break; + case BTN_TOOL_DOUBLETAP: + case BTN_TOOL_TRIPLETAP: case BTN_MIDDLE: pointer_dev_send_button(dev, 2, pressed); break; + case BTN_TOUCH: + dev->pointer.touchpaddown = true; + break; default: break; } From ad760dcda88e2fe5e869094dc505390a5c00b370 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 4 Nov 2025 12:22:12 +0100 Subject: [PATCH 13/17] Add scroll wheel support Signed-off-by: Jocelyn Falempe --- src/kmscon_terminal.c | 6 ++++++ src/uterm_input.c | 2 ++ src/uterm_input.h | 2 ++ src/uterm_input_internal.h | 1 + src/uterm_input_pointer.c | 13 +++++++++++++ 5 files changed, 24 insertions(+) diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index b645ccce..6e1eaffe 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -777,6 +777,12 @@ static void pointer_event(struct uterm_input *input, case UTERM_BUTTON: handle_pointer_button(term, ev); break; + case UTERM_WHEEL: + if (ev->wheel > 0) + tsm_screen_sb_up(term->console, 3); + else + tsm_screen_sb_down(term->console, 3); + break; case UTERM_SYNC: redraw_all(term); break; diff --git a/src/uterm_input.c b/src/uterm_input.c index 1da25486..93d991a1 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -466,6 +466,8 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, if (input_bit_is_set(relbits, REL_X) && input_bit_is_set(relbits, REL_Y)) capabilities |= UTERM_DEVICE_HAS_REL; + if (input_bit_is_set(relbits, REL_WHEEL)) + capabilities |= UTERM_DEVICE_HAS_WHEEL; } if (input_bit_is_set(evbits, EV_SYN) && diff --git a/src/uterm_input.h b/src/uterm_input.h index 8f375e2f..97067eb9 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -75,6 +75,7 @@ struct uterm_input_key_event { enum uterm_input_pointer_type { UTERM_MOVED, UTERM_BUTTON, + UTERM_WHEEL, UTERM_SYNC, }; @@ -82,6 +83,7 @@ struct uterm_input_pointer_event { enum uterm_input_pointer_type event; int32_t pointer_x; int32_t pointer_y; + int32_t wheel; uint8_t button; bool pressed; }; diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index f5de2466..10a01a1c 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -46,6 +46,7 @@ enum uterm_input_device_capability { UTERM_DEVICE_HAS_ABS = (1 << 3), UTERM_DEVICE_HAS_MOUSE_BTN = (1 << 4), UTERM_DEVICE_HAS_TOUCH = (1 << 5), + UTERM_DEVICE_HAS_WHEEL = (1 << 6), }; enum pointer_kind { diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c index ef04a89a..eeff91ff 100644 --- a/src/uterm_input_pointer.c +++ b/src/uterm_input_pointer.c @@ -17,6 +17,16 @@ static void pointer_dev_send_move(struct uterm_input_dev *dev) shl_hook_call(dev->input->pointer_hook, dev->input, &pev); } +static void pointer_dev_send_wheel(struct uterm_input_dev *dev, int32_t value) +{ + struct uterm_input_pointer_event pev = {0}; + + pev.event = UTERM_WHEEL; + pev.wheel = value; + + shl_hook_call(dev->input->pointer_hook, dev->input, &pev); +} + static void pointer_dev_send_button(struct uterm_input_dev *dev, uint8_t button, bool pressed) { struct uterm_input_pointer_event pev = {0}; @@ -58,6 +68,9 @@ void pointer_dev_rel(struct uterm_input_dev *dev, dev->pointer.y = dev->input->pointer_max_y; pointer_dev_send_move(dev); break; + case REL_WHEEL: + pointer_dev_send_wheel(dev, value); + break; default: break; } From db1e4c067d54f174cb0ed37688bf7ce6d237a482 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 10 Nov 2025 12:48:29 +0100 Subject: [PATCH 14/17] Add configuration to enable mouse support Mouse support is still experimental, so it's disabled by default. Also update the man page accordingly --- docs/man/kmscon.1.xml.in | 12 ++++++++++++ docs/man/kmscon.conf.1.xml.in | 12 ++++++++++++ src/kmscon_conf.c | 3 +++ src/kmscon_conf.h | 2 ++ src/kmscon_main.c | 2 +- src/kmscon_seat.c | 4 ++-- src/kmscon_seat.h | 2 +- src/kmscon_terminal.c | 8 +++++--- src/uterm_input.c | 7 +++++-- src/uterm_input.h | 2 +- tests/test_input.c | 2 +- 11 files changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/man/kmscon.1.xml.in b/docs/man/kmscon.1.xml.in index f38262a3..950ab73c 100644 --- a/docs/man/kmscon.1.xml.in +++ b/docs/man/kmscon.1.xml.in @@ -327,6 +327,18 @@ (default: 50) + + + + + Enable mouse/touchpad/trackpoint in kmscon (default: off). + It allows to select, and copy/paste text with a pointing device, + and also to scroll with the mouse wheel. + It's still experimental, and may not work with specific devices. + + + + Grabs / Keyboard Shortcuts: diff --git a/docs/man/kmscon.conf.1.xml.in b/docs/man/kmscon.conf.1.xml.in index d8b3bac6..0a3005d5 100644 --- a/docs/man/kmscon.conf.1.xml.in +++ b/docs/man/kmscon.conf.1.xml.in @@ -68,6 +68,7 @@ xkb-model=pc102 xkb-layout=de xkb-repeat-delay=200 xkb-repeat-rate=65 +mouse ### Video Options ### drm @@ -262,6 +263,17 @@ font-name=Ubuntu Mono + + + + Enable mouse/touchpad/trackpoint in kmscon (default: off). + It allows to select, and copy/paste text with a pointing device, + and also to scroll with the mouse wheel. + It's still experimental, and may not work with specific devices. + + + + ### Grabs/Keyboard-Shortcuts ### diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c index fd38bfd6..2be56f8c 100644 --- a/src/kmscon_conf.c +++ b/src/kmscon_conf.c @@ -110,6 +110,8 @@ static void print_help() "\t Initial delay for key-repeat in ms\n" "\t --xkb-repeat-rate [50]\n" "\t Delay between two key repeats in ms\n" + "\t --mouse [off]\n" + "\t Enable experimental mouse support\n" "\n" "Grabs / Keyboard-Shortcuts:\n" "\t --grab-scroll-up [Up]\n" @@ -733,6 +735,7 @@ int kmscon_conf_new(struct conf_ctx **out) CONF_OPTION_STRING(0, "xkb-compose-file", &conf->xkb_compose_file, ""), CONF_OPTION_UINT(0, "xkb-repeat-delay", &conf->xkb_repeat_delay, 250), CONF_OPTION_UINT(0, "xkb-repeat-rate", &conf->xkb_repeat_rate, 50), + CONF_OPTION_BOOL(0, "mouse", &conf->mouse, false), /* Grabs / Keyboard-Shortcuts */ CONF_OPTION_GRAB(0, "grab-scroll-up", &conf->grab_scroll_up, &def_grab_scroll_up), diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h index e62ba18b..1964d22a 100644 --- a/src/kmscon_conf.h +++ b/src/kmscon_conf.h @@ -111,6 +111,8 @@ struct kmscon_conf_t { unsigned int xkb_repeat_delay; /* keyboard key-repeat rate */ unsigned int xkb_repeat_rate; + /* Enable mouse support */ + bool mouse; /* Grabs / Keyboard-Shortcuts */ /* scroll-up grab */ diff --git a/src/kmscon_main.c b/src/kmscon_main.c index 14497134..2322d646 100644 --- a/src/kmscon_main.c +++ b/src/kmscon_main.c @@ -476,7 +476,7 @@ static void app_monitor_event(struct uterm_monitor *mon, case UTERM_MONITOR_INPUT: log_debug("new input device %s on seat %s", ev->dev_node, seat->name); - kmscon_seat_add_input(seat->seat, ev->dev_node); + kmscon_seat_add_input(seat->seat, ev->dev_node, seat->conf->mouse); break; } break; diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c index 007ff370..ade4433c 100644 --- a/src/kmscon_seat.c +++ b/src/kmscon_seat.c @@ -924,12 +924,12 @@ void kmscon_seat_refresh_display(struct kmscon_seat *seat, } } -int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node) +int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node, bool mouse) { if (!seat || !node) return -EINVAL; - uterm_input_add_dev(seat->input, node); + uterm_input_add_dev(seat->input, node, mouse); return 0; } diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h index 439e3582..a179caf4 100644 --- a/src/kmscon_seat.h +++ b/src/kmscon_seat.h @@ -90,7 +90,7 @@ void kmscon_seat_remove_display(struct kmscon_seat *seat, struct uterm_display *disp); void kmscon_seat_refresh_display(struct kmscon_seat *seat, struct uterm_display *disp); -int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node); +int kmscon_seat_add_input(struct kmscon_seat *seat, const char *node, bool mouse); void kmscon_seat_remove_input(struct kmscon_seat *seat, const char *node); const char *kmscon_seat_get_name(struct kmscon_seat *seat); diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 6e1eaffe..bbe3a2a0 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -1004,9 +1004,11 @@ int kmscon_terminal_register(struct kmscon_session **out, if (ret) goto err_ptyfd; - ret = uterm_input_register_pointer_cb(term->input, pointer_event, term); - if (ret) - goto err_input; + if (term->conf->mouse) { + ret = uterm_input_register_pointer_cb(term->input, pointer_event, term); + if (ret) + goto err_input; + } ret = kmscon_seat_register_session(seat, &term->session, session_event, term); diff --git a/src/uterm_input.c b/src/uterm_input.c index 93d991a1..ba3a4e57 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -496,7 +496,7 @@ static unsigned int probe_device_capabilities(struct uterm_input *input, #define HAS_ALL(caps, flags) (((caps) & (flags)) == (flags)) SHL_EXPORT -void uterm_input_add_dev(struct uterm_input *input, const char *node) +void uterm_input_add_dev(struct uterm_input *input, const char *node, bool mouse) { unsigned int capabilities; @@ -511,7 +511,10 @@ void uterm_input_add_dev(struct uterm_input *input, const char *node) if (HAS_ALL(capabilities, UTERM_DEVICE_HAS_REL | UTERM_DEVICE_HAS_MOUSE_BTN) || HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_TOUCH) || HAS_ALL(capabilities, UTERM_DEVICE_HAS_ABS | UTERM_DEVICE_HAS_MOUSE_BTN)) { - input_new_dev(input, node, capabilities); + if (mouse) + input_new_dev(input, node, capabilities); + else + llog_debug(input, "ignoring pointer device %s", node); } else { llog_debug(input, "ignoring non-useful device %s", node); } diff --git a/src/uterm_input.h b/src/uterm_input.h index 97067eb9..60854054 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -107,7 +107,7 @@ int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, void uterm_input_ref(struct uterm_input *input); void uterm_input_unref(struct uterm_input *input); -void uterm_input_add_dev(struct uterm_input *input, const char *node); +void uterm_input_add_dev(struct uterm_input *input, const char *node, bool mouse); void uterm_input_remove_dev(struct uterm_input *input, const char *node); int uterm_input_register_key_cb(struct uterm_input *input, uterm_input_key_cb cb, diff --git a/tests/test_input.c b/tests/test_input.c index 532579b8..ae1f1069 100644 --- a/tests/test_input.c +++ b/tests/test_input.c @@ -157,7 +157,7 @@ static void monitor_event(struct uterm_monitor *mon, uterm_input_unref(input); } else if (ev->type == UTERM_MONITOR_NEW_DEV) { if (ev->dev_type == UTERM_MONITOR_INPUT) - uterm_input_add_dev(input, ev->dev_node); + uterm_input_add_dev(input, ev->dev_node, true); } else if (ev->type == UTERM_MONITOR_FREE_DEV) { if (ev->dev_type == UTERM_MONITOR_INPUT) uterm_input_remove_dev(input, ev->dev_node); From fb75f9eb1a81876843f4d15e4a7afd247f04b01a Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Mon, 10 Nov 2025 14:27:15 +0100 Subject: [PATCH 15/17] Hide mouse pointer after 20s of inactivity. As the mouse pointer is not really transparent, it's better to hide it when it's not in use. --- src/kmscon_terminal.c | 3 +++ src/uterm_input.c | 20 +++++++++++++++++++- src/uterm_input.h | 1 + src/uterm_input_internal.h | 1 + src/uterm_input_pointer.c | 13 +++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index bbe3a2a0..73659eee 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -786,6 +786,9 @@ static void pointer_event(struct uterm_input *input, case UTERM_SYNC: redraw_all(term); break; + case UTERM_HIDE_TIMEOUT: + term->pointer.visible = false; + break; } } diff --git a/src/uterm_input.c b/src/uterm_input.c index ba3a4e57..cec0798e 100644 --- a/src/uterm_input.c +++ b/src/uterm_input.c @@ -295,6 +295,16 @@ static void input_free_dev(struct uterm_input_dev *dev) free(dev); } +static void hide_pointer_timer(struct ev_timer *timer, uint64_t num, void *data) +{ + struct uterm_input *input = data; + struct uterm_input_pointer_event pev; + + pev.event = UTERM_HIDE_TIMEOUT; + + shl_hook_call(input->pointer_hook, input, &pev); +} + SHL_EXPORT int uterm_input_new(struct uterm_input **out, struct ev_eloop *eloop, @@ -346,6 +356,11 @@ int uterm_input_new(struct uterm_input **out, if (ret) goto err_hook; + ret = ev_eloop_new_timer(input->eloop, &input->hide_pointer, NULL, + hide_pointer_timer, input); + if (ret) + goto err_hook_pointer; + /* xkbcommon won't use the XKB_DEFAULT_OPTIONS environment * variable if options is an empty string. * So if all variables are empty, use NULL instead. @@ -361,13 +376,16 @@ int uterm_input_new(struct uterm_input **out, ret = uxkb_desc_init(input, model, layout, variant, options, locale, keymap, compose_file, compose_file_len); if (ret) - goto err_hook_pointer; + goto err_hide_timer; llog_debug(input, "new object %p", input); ev_eloop_ref(input->eloop); *out = input; return 0; +err_hide_timer: + ev_eloop_rm_timer(input->hide_pointer); + err_hook_pointer: shl_hook_free(input->pointer_hook); diff --git a/src/uterm_input.h b/src/uterm_input.h index 60854054..ead838aa 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -77,6 +77,7 @@ enum uterm_input_pointer_type { UTERM_BUTTON, UTERM_WHEEL, UTERM_SYNC, + UTERM_HIDE_TIMEOUT, }; struct uterm_input_pointer_event { diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 10a01a1c..139ff307 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -115,6 +115,7 @@ struct uterm_input { struct shl_hook *pointer_hook; int32_t pointer_max_x; int32_t pointer_max_y; + struct ev_timer *hide_pointer; struct shl_dlist devices; }; diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c index eeff91ff..af695b33 100644 --- a/src/uterm_input_pointer.c +++ b/src/uterm_input_pointer.c @@ -1,11 +1,23 @@ #include #include +#include "eloop.h" #include "shl_hook.h" #include "shl_llog.h" #include "uterm_input.h" #include "uterm_input_internal.h" +static void pointer_update_inactivity_timer(struct uterm_input_dev *dev) +{ + struct itimerspec spec; + + spec.it_interval.tv_nsec = 0; + spec.it_interval.tv_sec = 0; + spec.it_value.tv_nsec = 0; + spec.it_value.tv_sec = 20; + ev_timer_update(dev->input->hide_pointer, &spec); +} + static void pointer_dev_send_move(struct uterm_input_dev *dev) { struct uterm_input_pointer_event pev = {0}; @@ -45,6 +57,7 @@ void pointer_dev_sync(struct uterm_input_dev *dev) pev.event = UTERM_SYNC; shl_hook_call(dev->input->pointer_hook, dev->input, &pev); + pointer_update_inactivity_timer(dev); dev->pointer.touchpaddown = false; } From 31a3a8b0fcda4263d33533a5214a2e88dc86219a Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Tue, 4 Nov 2025 15:19:29 +0100 Subject: [PATCH 16/17] Add double click word selection Select a word by double left click on it. It requires libtsm >= 4.3.0 to call tsm_screen_selection_word() Only handle double click for mouse left button as a first implementation Signed-off-by: Jocelyn Falempe --- meson.build | 2 +- src/kmscon_terminal.c | 10 ++++++++-- src/uterm_input.h | 1 + src/uterm_input_internal.h | 1 + src/uterm_input_pointer.c | 19 +++++++++++++++---- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/meson.build b/meson.build index af837860..2b5266ee 100644 --- a/meson.build +++ b/meson.build @@ -47,7 +47,7 @@ systemdsystemunitdir = systemd_deps.get_variable('systemdsystemunitdir', default fs = import('fs') xkbcommon_deps = dependency('xkbcommon', version: '>=0.5.0') -libtsm_deps = dependency('libtsm', version: '>=4.1.0') +libtsm_deps = dependency('libtsm', version: '>=4.3.0') libudev_deps = dependency('libudev', version: '>=172') dl_deps = dependency('dl') threads_deps = dependency('threads') diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 73659eee..4223267b 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -727,8 +727,14 @@ static void handle_pointer_button(struct kmscon_terminal *term, struct uterm_inp switch(ev->button) { case 0: if (ev->pressed) { - term->pointer.select = true; - start_selection(term->console, term->pointer.posx, term->pointer.posy); + if (ev->double_click) { + tsm_screen_selection_word(term->console, term->pointer.posx, term->pointer.posy); + copy_selection(term); + term->pointer.select = false; + } else { + term->pointer.select = true; + start_selection(term->console, term->pointer.posx, term->pointer.posy); + } } else { if (term->pointer.select) copy_selection(term); diff --git a/src/uterm_input.h b/src/uterm_input.h index ead838aa..55a05d9f 100644 --- a/src/uterm_input.h +++ b/src/uterm_input.h @@ -87,6 +87,7 @@ struct uterm_input_pointer_event { int32_t wheel; uint8_t button; bool pressed; + bool double_click; }; #define UTERM_INPUT_HAS_MODS(_ev, _mods) (((_ev)->mods & (_mods)) == (_mods)) diff --git a/src/uterm_input_internal.h b/src/uterm_input_internal.h index 139ff307..2a99a283 100644 --- a/src/uterm_input_internal.h +++ b/src/uterm_input_internal.h @@ -60,6 +60,7 @@ struct uterm_input_pointer { /* For pointers (mouse/trackpad/trackpoint/touchscreen) */ enum pointer_kind kind; enum uterm_input_pointer_type action; + struct timespec last_click; int32_t x; int32_t y; diff --git a/src/uterm_input_pointer.c b/src/uterm_input_pointer.c index af695b33..851c381d 100644 --- a/src/uterm_input_pointer.c +++ b/src/uterm_input_pointer.c @@ -1,3 +1,4 @@ +#include #include #include #include "eloop.h" @@ -39,13 +40,14 @@ static void pointer_dev_send_wheel(struct uterm_input_dev *dev, int32_t value) shl_hook_call(dev->input->pointer_hook, dev->input, &pev); } -static void pointer_dev_send_button(struct uterm_input_dev *dev, uint8_t button, bool pressed) +static void pointer_dev_send_button(struct uterm_input_dev *dev, uint8_t button, bool pressed, bool dbl_click) { struct uterm_input_pointer_event pev = {0}; pev.event = UTERM_BUTTON; pev.button = button; pev.pressed = pressed; + pev.double_click = dbl_click; shl_hook_call(dev->input->pointer_hook, dev->input, &pev); } @@ -142,19 +144,28 @@ void pointer_dev_abs(struct uterm_input_dev *dev, void pointer_dev_button(struct uterm_input_dev *dev, uint16_t code, int32_t value) { + struct timespec tp; + uint64_t elapsed; bool pressed = (value == 1); + bool dbl_click = false; switch (code) { case BTN_LEFT: - pointer_dev_send_button(dev, 0, pressed); + if (pressed) { + clock_gettime(CLOCK_MONOTONIC, &tp); + elapsed = (tp.tv_sec - dev->pointer.last_click.tv_sec) * 1000 + (tp.tv_nsec - dev->pointer.last_click.tv_nsec) / 1000000; + dbl_click = (elapsed < 500); + dev->pointer.last_click = tp; + } + pointer_dev_send_button(dev, 0, pressed, dbl_click); break; case BTN_RIGHT: - pointer_dev_send_button(dev, 1, pressed); + pointer_dev_send_button(dev, 1, pressed, false); break; case BTN_TOOL_DOUBLETAP: case BTN_TOOL_TRIPLETAP: case BTN_MIDDLE: - pointer_dev_send_button(dev, 2, pressed); + pointer_dev_send_button(dev, 2, pressed, false); break; case BTN_TOUCH: dev->pointer.touchpaddown = true; From e78393b362a5d3381df0844571b6de3bb9022308 Mon Sep 17 00:00:00 2001 From: Jocelyn Falempe Date: Fri, 21 Nov 2025 13:15:53 +0100 Subject: [PATCH 17/17] Fix mouse pointer stuck at up left corner Handle the case where the screen is not used, and its width and height are set to 0. Suggested-by: Geo Carncross Signed-off-by: Jocelyn Falempe --- src/kmscon_terminal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c index 4223267b..d5be714c 100644 --- a/src/kmscon_terminal.c +++ b/src/kmscon_terminal.c @@ -276,6 +276,9 @@ static void update_pointer_max_all(struct kmscon_terminal *term) sw = uterm_mode_get_height(mode); sh = uterm_mode_get_width(mode); } + if (!sw || !sh) + continue; + if (sw < max_x) max_x = sw; if (sh < max_y)