Control: tags 1138246 + patch Control: tags 1138246 + pending
Dear maintainer, I've prepared an NMU for libtsm (versioned as 4.5.0-0.1) and uploaded it to DELAYED/5. Please feel free to tell me if I should cancel it. Regards. diffstat for libtsm-4.4.3 libtsm-4.5.0 NEWS.md | 20 +- debian/changelog | 9 debian/control | 2 debian/libtsm4.symbols | 3 external/wcwidth/wcwidth.h | 9 meson.build | 2 src/shared/shl_dlist.h | 132 ++++++++++++++ src/tsm/libtsm-int.h | 36 ++- src/tsm/libtsm.h | 15 + src/tsm/libtsm.sym | 6 src/tsm/tsm-render.c | 33 +-- src/tsm/tsm-screen.c | 321 ++++++++++++++-------------------- src/tsm/tsm-selection.c | 420 +++++++++++++++------------------------------ src/tsm/tsm-vte.c | 50 ++++- test/test_screen.c | 28 +++ test/test_selection.c | 146 ++++++++++----- test/test_vte.c | 210 ++++++++++++++++++++++ 17 files changed, 891 insertions(+), 551 deletions(-) diff -Nru libtsm-4.4.3/debian/changelog libtsm-4.5.0/debian/changelog --- libtsm-4.4.3/debian/changelog 2026-04-03 19:06:22.000000000 -0400 +++ libtsm-4.5.0/debian/changelog 2026-06-03 13:54:39.000000000 -0400 @@ -1,3 +1,12 @@ +libtsm (4.5.0-0.1) unstable; urgency=medium + + * Non-maintainer upload. + * New upstream release. (Closes: #1138246) + * debian/control: Bump Standards-Version to 4.7.4. + * debian/libtsm4.symbols: Record new symbols. + + -- Boyuan Yang <[email protected]> Wed, 03 Jun 2026 13:54:39 -0400 + libtsm (4.4.3-0.1) unstable; urgency=medium * Non-maintainer upload. diff -Nru libtsm-4.4.3/debian/control libtsm-4.5.0/debian/control --- libtsm-4.4.3/debian/control 2026-04-03 19:03:08.000000000 -0400 +++ libtsm-4.5.0/debian/control 2026-06-03 13:48:50.000000000 -0400 @@ -7,7 +7,7 @@ debhelper-compat (= 13), libxkbcommon-dev, pkgconf, -Standards-Version: 4.7.2 +Standards-Version: 4.7.4 Homepage: https://github.com/kmscon/libtsm Vcs-Browser: https://salsa.debian.org/debian/libtsm Vcs-Git: https://salsa.debian.org/debian/libtsm.git diff -Nru libtsm-4.4.3/debian/libtsm4.symbols libtsm-4.5.0/debian/libtsm4.symbols --- libtsm-4.4.3/debian/libtsm4.symbols 2026-04-03 19:03:08.000000000 -0400 +++ libtsm-4.5.0/debian/libtsm4.symbols 2026-06-03 13:54:36.000000000 -0400 @@ -7,6 +7,7 @@ LIBTSM_4_1@LIBTSM_4_1 4.3.0 LIBTSM_4_3@LIBTSM_4_3 4.3.0 LIBTSM_4_4@LIBTSM_4_4 4.4.2 + LIBTSM_4_5@LIBTSM_4_5 4.5.0 tsm_screen_clear_sb@LIBTSM_3 4.3.0 tsm_screen_delete_chars@LIBTSM_3 4.3.0 tsm_screen_delete_lines@LIBTSM_3 4.3.0 @@ -80,7 +81,9 @@ tsm_vte_ref@LIBTSM_3 4.3.0 tsm_vte_reset@LIBTSM_3 4.3.0 tsm_vte_set_backspace_sends_delete@LIBTSM_4_1 4.3.0 + tsm_vte_set_bell_cb@LIBTSM_4_5 4.5.0 tsm_vte_set_custom_palette@LIBTSM_4 4.3.0 + tsm_vte_set_led_cb@LIBTSM_4_5 4.5.0 tsm_vte_set_mouse_cb@LIBTSM_4_1 4.3.0 tsm_vte_set_osc_cb@LIBTSM_3 4.3.0 tsm_vte_set_palette@LIBTSM_3 4.3.0 diff -Nru libtsm-4.4.3/external/wcwidth/wcwidth.h libtsm-4.5.0/external/wcwidth/wcwidth.h --- libtsm-4.4.3/external/wcwidth/wcwidth.h 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/external/wcwidth/wcwidth.h 2026-04-21 05:27:37.000000000 -0400 @@ -3,10 +3,15 @@ #include <stdlib.h> -__BEGIN_DECLS +#ifdef __cplusplus +extern "C" { +#endif int wcwidth(wchar_t ucs); -__END_DECLS +#ifdef __cplusplus +} +#endif + #endif diff -Nru libtsm-4.4.3/meson.build libtsm-4.5.0/meson.build --- libtsm-4.4.3/meson.build 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/meson.build 2026-04-21 05:27:37.000000000 -0400 @@ -3,7 +3,7 @@ project( 'libtsm', 'c', - version: '4.4.3', + version: '4.5.0', license: 'MIT', meson_version: '>=1.1', default_options: [ diff -Nru libtsm-4.4.3/NEWS.md libtsm-4.5.0/NEWS.md --- libtsm-4.4.3/NEWS.md 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/NEWS.md 2026-04-21 05:27:37.000000000 -0400 @@ -1,5 +1,21 @@ # libtsm Release News +## CHANGES WITH 4.5.0 +### New features +* Support for terminal bell and keyboard LEDs by @aruiz in https://github.com/kmscon/libtsm/pull/34 +### Bug fixes +* Fix build musl by @kdj0c in https://github.com/kmscon/libtsm/pull/32 +* test/vte: fix memory leak in vte tests by @kdj0c in https://github.com/kmscon/libtsm/pull/35 +* Refactor scrollback and selection by @kdj0c in https://github.com/kmscon/libtsm/pull/36 +* screen: Fix wrong attribute for new cells when resizing by @kdj0c in https://github.com/kmscon/libtsm/pull/37 +* Fix remove from sb by @kdj0c in https://github.com/kmscon/libtsm/pull/38 +* Fix get next line by @kdj0c in https://github.com/kmscon/libtsm/pull/39 +* Fix scrollback position by @kdj0c in https://github.com/kmscon/libtsm/pull/40 +* test: robustness, make the test faster. by @kdj0c in https://github.com/kmscon/libtsm/pull/41 + +### New Contributors +* @aruiz made their first contribution in https://github.com/kmscon/libtsm/pull/34 + ## CHANGES WITH 4.4.3 ### New features * Add support for VT200 mouse tracking by @caramelli in https://github.com/kmscon/libtsm/pull/27 @@ -9,7 +25,7 @@ * vte: guard case 'm' (SGR) against CSI_GT prefix by @kdj0c in https://github.com/kmscon/libtsm/pull/29 * wcwidth: update to upstream v4 by @kdj0c in https://github.com/kmscon/libtsm/pull/28 -## New Contributors +### New Contributors * @Karlson2k made their first contribution in https://github.com/kmscon/libtsm/pull/24 * @caramelli made their first contribution in https://github.com/kmscon/libtsm/pull/26 @@ -21,7 +37,7 @@ * Fix CSI 18t and 19t reporting size. by @kdj0c in https://github.com/kmscon/libtsm/pull/20 * resize: Fix a corner case when resizing by @kdj0c in https://github.com/kmscon/libtsm/pull/22 -## New Contributors +### New Contributors * @1ace made their first contribution in https://github.com/kmscon/libtsm/pull/19 ## CHANGES WITH 4.4.1 diff -Nru libtsm-4.4.3/src/shared/shl_dlist.h libtsm-4.5.0/src/shared/shl_dlist.h --- libtsm-4.4.3/src/shared/shl_dlist.h 1969-12-31 19:00:00.000000000 -0500 +++ libtsm-4.5.0/src/shared/shl_dlist.h 2026-04-21 05:27:37.000000000 -0400 @@ -0,0 +1,132 @@ +/* + * shl - Double Linked List + * + * Copyright (c) 2011-2012 David Herrmann <[email protected]> + * Copyright (c) 2011 University of Tuebingen + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * A simple double linked list implementation + */ + +#ifndef SHL_DLIST_H +#define SHL_DLIST_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +/* miscellaneous */ + +#define shl_offsetof(pointer, type, member) \ + ({ \ + const typeof(((type *)0)->member) *__ptr = (pointer); \ + (type *)(((char *)__ptr) - offsetof(type, member)); \ + }) + +/* double linked list */ + +struct shl_dlist { + struct shl_dlist *next; + struct shl_dlist *prev; +}; + +#define SHL_DLIST_INIT(head) {&(head), &(head)} + +static inline void shl_dlist_init(struct shl_dlist *list) +{ + list->next = list; + list->prev = list; +} + +static inline void shl_dlist__link(struct shl_dlist *prev, struct shl_dlist *next, + struct shl_dlist *n) +{ + next->prev = n; + n->next = next; + n->prev = prev; + prev->next = n; +} + +static inline void shl_dlist_link(struct shl_dlist *head, struct shl_dlist *n) +{ + return shl_dlist__link(head, head->next, n); +} + +static inline void shl_dlist_link_tail(struct shl_dlist *head, struct shl_dlist *n) +{ + return shl_dlist__link(head->prev, head, n); +} + +static inline void shl_dlist__unlink(struct shl_dlist *prev, struct shl_dlist *next) +{ + next->prev = prev; + prev->next = next; +} + +static inline void shl_dlist_unlink(struct shl_dlist *e) +{ + shl_dlist__unlink(e->prev, e->next); + e->prev = NULL; + e->next = NULL; +} + +static inline bool shl_dlist_empty(struct shl_dlist *head) +{ + return head->next == head; +} + +#define shl_dlist_entry(ptr, type, member) shl_offsetof((ptr), type, member) + +#define shl_dlist_first(head, type, member) shl_dlist_entry((head)->next, type, member) + +#define shl_dlist_last(head, type, member) shl_dlist_entry((head)->prev, type, member) + +#define shl_dlist_next(iter, head, member) \ + ((iter)->member.next == (head) ? NULL : shl_dlist_entry((iter)->member.next, typeof(*iter), list)) + +#define shl_dlist_prev(iter, head, member) \ + ((iter)->member.prev == (head) ? NULL : shl_dlist_entry((iter)->member.prev, typeof(*iter), list)) + +#define shl_dlist_for_each(iter, head) for (iter = (head)->next; iter != (head); iter = iter->next) + +#define shl_dlist_for_each_but_one(iter, start, head) \ + for (iter = ((start)->next == (head)) ? (start)->next->next : (start)->next; \ + iter != (start); \ + iter = (iter->next == (head) && (start) != (head)) ? iter->next->next : iter->next) + +#define shl_dlist_for_each_safe(iter, tmp, head) \ + for (iter = (head)->next, tmp = iter->next; iter != (head); iter = tmp, tmp = iter->next) + +#define shl_dlist_for_each_reverse(iter, head) \ + for (iter = (head)->prev; iter != (head); iter = iter->prev) + +#define shl_dlist_for_each_reverse_but_one(iter, start, head) \ + for (iter = ((start)->prev == (head)) ? (start)->prev->prev : (start)->prev; \ + iter != (start); \ + iter = (iter->prev == (head) && (start) != (head)) ? iter->prev->prev : iter->prev) + +#define shl_dlist_for_each_reverse_safe(iter, tmp, head) \ + for (iter = (head)->prev, tmp = iter->prev; iter != (head); iter = tmp, tmp = iter->prev) + +#endif /* SHL_DLIST_H */ diff -Nru libtsm-4.4.3/src/tsm/libtsm.h libtsm-4.5.0/src/tsm/libtsm.h --- libtsm-4.4.3/src/tsm/libtsm.h 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/libtsm.h 2026-04-21 05:27:37.000000000 -0400 @@ -418,6 +418,19 @@ bool track_pixels, void *data); +typedef void (*tsm_vte_bell_cb) (struct tsm_vte *vte, + void *data); + +enum tsm_vte_led { + TSM_VTE_LED_SCROLL_LOCK = (1 << 0), + TSM_VTE_LED_NUM_LOCK = (1 << 1), + TSM_VTE_LED_CAPS_LOCK = (1 << 2), +}; + +typedef void (*tsm_vte_led_cb) (struct tsm_vte *vte, + unsigned int leds, + void *data); + int tsm_vte_new(struct tsm_vte **out, struct tsm_screen *con, tsm_vte_write_cb write_cb, void *data, tsm_log_t log, void *log_data); @@ -426,6 +439,8 @@ void tsm_vte_set_osc_cb(struct tsm_vte *vte, tsm_vte_osc_cb osc_cb, void *osc_data); void tsm_vte_set_mouse_cb(struct tsm_vte *vte, tsm_vte_mouse_cb mouse_cb, void *mouse_data); +void tsm_vte_set_bell_cb(struct tsm_vte *vte, tsm_vte_bell_cb bell_cb, void *bell_data); +void tsm_vte_set_led_cb(struct tsm_vte *vte, tsm_vte_led_cb led_cb, void *led_data); /** * @brief Set color palette to one of the predefined palette on the vte object. diff -Nru libtsm-4.4.3/src/tsm/libtsm-int.h libtsm-4.5.0/src/tsm/libtsm-int.h --- libtsm-4.4.3/src/tsm/libtsm-int.h 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/libtsm-int.h 2026-04-21 05:27:37.000000000 -0400 @@ -32,6 +32,7 @@ #include <stdlib.h> #include <stdint.h> #include "libtsm.h" +#include "shl_dlist.h" #include "shl-llog.h" #define SHL_EXPORT __attribute__((visibility("default"))) @@ -87,20 +88,26 @@ }; struct line { - struct line *next; /* next line (NULL if not sb) */ - struct line *prev; /* prev line (NULL if not sb) */ - + struct shl_dlist list; /* list node, next/prev are NULL if not in sb */ unsigned int size; /* real width */ struct cell *cells; /* actuall cells */ - uint64_t sb_id; /* sb ID */ + uint64_t sb_id; /* sb ID, 0 if not in sb */ tsm_age_t age; /* age of the whole line */ }; -#define SELECTION_TOP -1 struct selection_pos { - struct line *line; - unsigned int x; - int y; + unsigned int x; /* x offset from the start of the line */ + struct line *line; /* line the selection is on */ +}; + +struct tsm_scrollback { + /* scroll-back buffer */ + unsigned int count; /* number of lines in sb */ + struct shl_dlist list; /* list of lines in sb */ + unsigned int max; /* max-limit of lines in sb */ + struct line *pos; /* current position in sb or NULL */ + unsigned int pos_num; /* current numeric position in sb */ + uint64_t last_id; /* last id given to sb-line */ }; struct tsm_screen { @@ -134,14 +141,7 @@ struct line **alt_lines; /* real alternative lines */ tsm_age_t age; /* whole screen age */ - /* scroll-back buffer */ - unsigned int sb_count; /* number of lines in sb */ - struct line *sb_first; /* first line; was moved first */ - struct line *sb_last; /* last line; was moved last*/ - unsigned int sb_max; /* max-limit of lines in sb */ - struct line *sb_pos; /* current position in sb or NULL */ - unsigned int sb_pos_num; /* current numeric position in sb */ - uint64_t sb_last_id; /* last id given to sb-line */ + struct tsm_scrollback sb; /* cursor: positions are always in-bound, but cursor_x might be * bigger than size_x if new-line is pending */ @@ -172,6 +172,10 @@ } } +static inline bool is_in_scrollback(struct selection_pos *sel) { + return (sel->line && sel->line->sb_id); +} + /* available character sets */ typedef tsm_symbol_t tsm_vte_charset[96]; diff -Nru libtsm-4.4.3/src/tsm/libtsm.sym libtsm-4.5.0/src/tsm/libtsm.sym --- libtsm-4.4.3/src/tsm/libtsm.sym 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/libtsm.sym 2026-04-21 05:27:37.000000000 -0400 @@ -142,3 +142,9 @@ global: tsm_vte_paste; } LIBTSM_4_3; + +LIBTSM_4_5 { +global: + tsm_vte_set_bell_cb; + tsm_vte_set_led_cb; +} LIBTSM_4_4; diff -Nru libtsm-4.4.3/src/tsm/tsm-render.c libtsm-4.5.0/src/tsm/tsm-render.c --- libtsm-4.4.3/src/tsm/tsm-render.c 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/tsm-render.c 2026-04-21 05:27:37.000000000 -0400 @@ -38,6 +38,7 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-render" @@ -47,7 +48,7 @@ { unsigned int cur_x, cur_y; unsigned int i, j, k; - struct line *iter, *line = NULL; + struct line *line, *next_line = NULL; struct cell *cell, empty; struct tsm_screen_attr attr; int ret, warned = 0; @@ -71,47 +72,41 @@ cur_y = con->size_y - 1; /* push each character into rendering pipeline */ - - iter = con->sb_pos; k = 0; + next_line = con->sb.pos; if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y == SELECTION_TOP) + if (!con->sel_start.line) in_sel = !in_sel; - if (!con->sel_end.line && con->sel_end.y == SELECTION_TOP) + if (!con->sel_end.line) in_sel = !in_sel; - if (con->sel_start.line && - (!iter || con->sel_start.line->sb_id < iter->sb_id)) + if (is_in_scrollback(&con->sel_start) && + (!con->sb.pos || con->sel_start.line->sb_id < con->sb.pos->sb_id)) in_sel = !in_sel; - if (con->sel_end.line && - (!iter || con->sel_end.line->sb_id < iter->sb_id)) + if (is_in_scrollback(&con->sel_end) && + (!con->sb.pos || con->sel_end.line->sb_id < con->sb.pos->sb_id)) in_sel = !in_sel; } for (i = 0; i < con->size_y; ++i) { - if (iter) { - line = iter; - iter = iter->next; + if (next_line) { + line = next_line; + next_line = shl_dlist_next(next_line, &con->sb.list, list); } else { line = con->lines[k]; k++; } if (con->sel_active) { - if (con->sel_start.line == line || - (!con->sel_start.line && - con->sel_start.y == k - 1)) + if (con->sel_start.line == line) sel_start = true; else sel_start = false; - if (con->sel_end.line == line || - (!con->sel_end.line && - con->sel_end.y == k - 1)) + if (con->sel_end.line == line) sel_end = true; else sel_end = false; - was_sel = false; } diff -Nru libtsm-4.4.3/src/tsm/tsm-screen.c libtsm-4.5.0/src/tsm/tsm-screen.c --- libtsm-4.4.3/src/tsm/tsm-screen.c 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/tsm-screen.c 2026-04-21 05:27:37.000000000 -0400 @@ -64,6 +64,8 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl-macro.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-screen" @@ -110,7 +112,7 @@ c->age = con->age_cnt; } -void screen_cell_init_generic(struct tsm_screen *con, struct cell *cell, struct tsm_screen_attr *attr) +static void screen_cell_init_generic(struct tsm_screen *con, struct cell *cell, struct tsm_screen_attr *attr) { cell->ch = 0; cell->width = 1; @@ -136,8 +138,9 @@ line = malloc(sizeof(*line)); if (!line) return -ENOMEM; - line->next = NULL; - line->prev = NULL; + line->list.next = NULL; + line->list.prev = NULL; + line->sb_id = 0; line->size = width; line->age = con->age_cnt; @@ -184,6 +187,16 @@ return 0; } +static void clear_selection_on_line(struct tsm_screen *con, struct line *line) +{ + if (!con->sel_active) + return; + if (con->sel_start.line == line) + con->sel_start.line = NULL; + if (con->sel_end.line == line) + con->sel_end.line = NULL; +} + /* This links the given line into the scrollback-buffer */ static void link_to_scrollback(struct tsm_screen *con, struct line *line) { @@ -192,118 +205,81 @@ /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - if (con->sb_max == 0) { - if (con->sel_active) { - if (con->sel_start.line == line) { - con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; - } - if (con->sel_end.line == line) { - con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; - } - } + if (con->sb.max == 0) { + clear_selection_on_line(con, line); line_free(line); return; } /* Remove a line from the scrollback buffer if it reaches its maximum. * We must take care to correctly keep the current position as the new - * line is linked in after we remove the top-most line here. - * sb_max == 0 is tested earlier so we can assume sb_max > 0 here. In - * other words, buf->sb_first is a valid line if sb_count >= sb_max. */ - if (con->sb_count >= con->sb_max) { - tmp = con->sb_first; - con->sb_first = tmp->next; - if (tmp->next) - tmp->next->prev = NULL; - else - con->sb_last = NULL; - --con->sb_count; - - /* (position == tmp && !next) means we have sb_max=1 so set - * position to the new line. Otherwise, set to new first line. - * If position!=tmp and we have a fixed-position then nothing - * needs to be done because we can stay at the same line. If we - * have no fixed-position, we need to set the position to the - * next inserted line, which can be "line", too. */ - if (con->sb_pos) { - if (con->sb_pos == tmp || - !(con->flags & TSM_SCREEN_FIXED_POS)) { - if (con->sb_pos->next) { - con->sb_pos = con->sb_pos->next; - ++con->sb_pos_num; - } else { - con->sb_pos = line; - con->sb_pos_num = 0; - } - } - } - - if (con->sel_active) { - if (con->sel_start.line == tmp) { - con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; - } - if (con->sel_end.line == tmp) { - con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; - } + * line is linked in after we remove the top-most line here. */ + if (con->sb.count >= con->sb.max) { + tmp = shl_dlist_first(&con->sb.list, struct line, list); + shl_dlist_unlink(&tmp->list); + --con->sb.count; + + /* Only consider sb.max > 1, so there is always another line in sb. */ + if (con->sb.pos == tmp) { + con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); + con->sb.pos_num = 0; + } else { + con->sb.pos_num--; } + clear_selection_on_line(con, tmp); line_free(tmp); } - line->sb_id = ++con->sb_last_id; - line->next = NULL; - line->prev = con->sb_last; - if (con->sb_last) { - con->sb_last->next = line; - } else { - con->sb_first = line; - } - con->sb_last = line; - ++con->sb_count; - - if (con->sb_pos == NULL) { - con->sb_pos_num = con->sb_count; - } + line->sb_id = ++con->sb.last_id; + shl_dlist_link_tail(&con->sb.list, &line->list); + ++con->sb.count; + if (con->sb.pos == NULL) + con->sb.pos_num = con->sb.count; } /* Remove num lines from scroll back to current buffer */ static void remove_from_sb(struct tsm_screen *con, unsigned int num) { struct line *tmp; + int i, copy_len; /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - if (!con->sb_max || !con->sb_count || !con->sb_last) + if (!con->sb.max || !con->sb.count || shl_dlist_empty(&con->sb.list)) return; - if (num > con->sb_count) - num = con->sb_count; + if (num > con->sb.count) + num = con->sb.count; while (num--) { - tmp = con->sb_last; - con->sb_last = tmp->prev; - - if (tmp->prev) - tmp->prev->next = NULL; - else - con->sb_first = NULL; - --con->sb_count; - - tmp->next = NULL; - tmp->prev = NULL; - tmp->sb_id = 0; - - if (con->sb_pos == tmp) { - con->sb_pos_num = 0; - con->sb_pos = NULL; + tmp = shl_dlist_last(&con->sb.list, struct line, list); + shl_dlist_unlink(&tmp->list); + --con->sb.count; + + if (con->sb.pos == tmp) { + con->sb.pos_num = con->sb.count; + con->sb.pos = NULL; } - memcpy(con->lines[num], tmp, sizeof(*tmp)); - free(tmp); + /* + * Copy the cells from the scrollback buffer to the line. scrollback buffer can have a different + * size as current lines, because resizing doesn't resize lines in scrollback buffer. + */ + copy_len = shl_min(tmp->size, con->lines[num]->size); + memcpy(con->lines[num]->cells, tmp->cells, copy_len * sizeof(struct cell)); + for (i = copy_len; i < con->size_x; i++) + screen_cell_init(con, &con->lines[num]->cells[i]); + con->lines[num]->age = con->age_cnt; + + if (con->sel_active && con->sel_start.line == tmp) + con->sel_start.line = con->lines[num]; + if (con->sel_active && con->sel_end.line == tmp) + con->sel_end.line = con->lines[num]; + + line_free(tmp); } + if (!con->sb.pos) + con->sb.pos_num = con->sb.count; } static void screen_scroll_up(struct tsm_screen *con, unsigned int num) @@ -356,27 +332,6 @@ memcpy(&con->lines[con->margin_top + (max - num)], cache, num * sizeof(struct line*)); - - if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y >= 0) { - con->sel_start.y -= num; - if (con->sel_start.y < 0) { - con->sel_start.line = con->sb_last; - while (con->sel_start.line && ++con->sel_start.y < 0) - con->sel_start.line = con->sel_start.line->prev; - con->sel_start.y = SELECTION_TOP; - } - } - if (!con->sel_end.line && con->sel_end.y >= 0) { - con->sel_end.y -= num; - if (con->sel_end.y < 0) { - con->sel_end.line = con->sb_last; - while (con->sel_end.line && ++con->sel_end.y < 0) - con->sel_end.line = con->sel_end.line->prev; - con->sel_end.y = SELECTION_TOP; - } - } - } } static void screen_scroll_down(struct tsm_screen *con, unsigned int num) @@ -414,13 +369,6 @@ memcpy(&con->lines[con->margin_top], cache, num * sizeof(struct line*)); - - if (con->sel_active) { - if (!con->sel_start.line && con->sel_start.y >= 0) - con->sel_start.y += num; - if (!con->sel_end.line && con->sel_end.y >= 0) - con->sel_end.y += num; - } } static void screen_write(struct tsm_screen *con, unsigned int x, @@ -510,6 +458,12 @@ return con->margin_top + y; } +static void reset_scrollback_position(struct tsm_screen *con) +{ + con->sb.pos = NULL; + con->sb.pos_num = con->sb.count; +} + SHL_EXPORT int tsm_screen_new(struct tsm_screen **out, tsm_log_t log, void *log_data) { @@ -533,6 +487,7 @@ con->def_attr.fr = 255; con->def_attr.fg = 255; con->def_attr.fb = 255; + shl_dlist_init(&con->sb.list); ret = tsm_symbol_table_new(&con->sym_table); if (ret) @@ -578,17 +533,16 @@ return; llog_debug(con, "destroying screen"); + tsm_screen_clear_sb(con); for (i = 0; i < con->line_num; ++i) { line_free(con->main_lines[i]); line_free(con->alt_lines[i]); } - free(con->main_lines); free(con->alt_lines); free(con->tab_ruler); tsm_symbol_table_unref(con->sym_table); - tsm_screen_clear_sb(con); free(con); } @@ -767,7 +721,7 @@ /* scroll buffer if screen height shrinks */ if (y < con->size_y) { diff = con->size_y - y; - if (!con->sb_last || (con->flags & TSM_SCREEN_ALTERNATE)) { + if (shl_dlist_empty(&con->sb.list) || (con->flags & TSM_SCREEN_ALTERNATE)) { /* If there is nothing in the scrollback buffer, * Only scroll up if the cursor would go off-screen */ if (con->cursor_y >= y) { @@ -784,8 +738,8 @@ } } else if (y > con->size_y) { diff = y - con->size_y; - if (diff > con->sb_count) - diff = con->sb_count; + if (diff > con->sb.count) + diff = con->sb.count; /* * When increasing the terminal number of rows, we can move some * lines from the scrollback buffer to the main buffer. @@ -835,7 +789,7 @@ return 0; } -/* set maximum scrollback buffer size */ +/* set maximum scrollback buffer size in number of lines*/ SHL_EXPORT void tsm_screen_set_max_sb(struct tsm_screen *con, unsigned int max) @@ -845,45 +799,37 @@ if (!con) return; + // Don't allow only one line in the scrollback buffer, this simplifies + // the code, and is not a useful usecase. + if (max == 1) + max = 2; + screen_inc_age(con); /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - while (con->sb_count > max) { - line = con->sb_first; - con->sb_first = line->next; - if (line->next) - line->next->prev = NULL; - else - con->sb_last = NULL; - con->sb_count--; + while (con->sb.count > max) { + line = shl_dlist_first(&con->sb.list, struct line, list); + shl_dlist_unlink(&line->list); + --con->sb.count; /* We treat fixed/unfixed position the same here because we * remove lines from the TOP of the scrollback buffer. */ - if (con->sb_pos == line) - con->sb_pos = con->sb_first; + if (con->sb.pos == line) + con->sb.pos = shl_dlist_first(&con->sb.list, struct line, list); - if (con->sel_active) { - if (con->sel_start.line == line) { - con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; - } - if (con->sel_end.line == line) { - con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; - } - } + clear_selection_on_line(con, line); line_free(line); } - - con->sb_max = max; + con->sb.max = max; } /* clear scrollback buffer */ SHL_EXPORT void tsm_screen_clear_sb(struct tsm_screen *con) { - struct line *iter, *tmp; + struct shl_dlist *iter, *safe; + struct line *tmp; if (!con) return; @@ -892,33 +838,27 @@ /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - for (iter = con->sb_first; iter; ) { - tmp = iter; - iter = iter->next; - line_free(tmp); - } - - con->sb_first = NULL; - con->sb_last = NULL; - con->sb_count = 0; - con->sb_pos = NULL; - con->sb_pos_num = 0; - if (con->sel_active) { - if (con->sel_start.line) { + if (con->sel_start.line && is_in_scrollback(&con->sel_start)) con->sel_start.line = NULL; - con->sel_start.y = SELECTION_TOP; - } - if (con->sel_end.line) { + if (con->sel_end.line && is_in_scrollback(&con->sel_end)) con->sel_end.line = NULL; - con->sel_end.y = SELECTION_TOP; - } } + shl_dlist_for_each_safe(iter, safe, &con->sb.list) { + tmp = shl_dlist_entry(iter, struct line, list); + shl_dlist_unlink(&tmp->list); + line_free(tmp); + } + con->sb.count = 0; + con->sb.pos = NULL; + con->sb.pos_num = 0; } SHL_EXPORT void tsm_screen_sb_up(struct tsm_screen *con, unsigned int num) { + struct line *prev; + if (!con || !num) return; @@ -926,18 +866,25 @@ /* TODO: more sophisticated ageing */ con->age = con->age_cnt; + if (shl_dlist_empty(&con->sb.list)) + return; + while (num--) { - if (con->sb_pos) { - if (!con->sb_pos->prev) + if (con->sb.pos) { + if (con->sb.pos_num == 0) return; - con->sb_pos = con->sb_pos->prev; - --con->sb_pos_num; - } else if (!con->sb_last) { - return; + prev = shl_dlist_prev(con->sb.pos, &con->sb.list, list); + if (!prev) { + llog_error(con, "prev is NULL, con->sb.pos_num: %d con->sb.count: %d", + con->sb.pos_num, con->sb.count); + return; + } + --con->sb.pos_num; + con->sb.pos = prev; } else { - con->sb_pos = con->sb_last; - con->sb_pos_num = con->sb_count - 1; + con->sb.pos = shl_dlist_last(&con->sb.list, struct line, list); + con->sb.pos_num = con->sb.count - 1; } } } @@ -952,14 +899,12 @@ /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - while (num--) { - if (con->sb_pos) { - con->sb_pos = con->sb_pos->next; - ++con->sb_pos_num; - } - else - return; + while (num-- && con->sb.pos && con->sb.pos_num < con->sb.count) { + con->sb.pos = shl_dlist_next(con->sb.pos, &con->sb.list, list); + ++con->sb.pos_num; } + if (con->sb.pos_num == con->sb.count) + con->sb.pos = NULL; } SHL_EXPORT @@ -985,15 +930,15 @@ SHL_EXPORT void tsm_screen_sb_reset(struct tsm_screen *con) { - if (!con || !con->sb_pos) + if (!con || !con->sb.pos) return; screen_inc_age(con); /* TODO: more sophisticated ageing */ con->age = con->age_cnt; - con->sb_pos = NULL; - con->sb_pos_num = con->sb_count; + con->sb.pos = NULL; + con->sb.pos_num = con->sb.count; } unsigned int tsm_screen_sb_get_line_count(struct tsm_screen *con) @@ -1002,7 +947,7 @@ return 0; } - return con->sb_count; + return con->sb.count; } unsigned int tsm_screen_sb_get_line_pos(struct tsm_screen *con) @@ -1011,7 +956,7 @@ return 0; } - return con->sb_pos_num; + return con->sb.pos_num; } SHL_EXPORT @@ -1021,6 +966,8 @@ if (!con || !attr) return; memcpy(&con->def_attr, attr, sizeof(*attr)); + if (!(con->flags & TSM_SCREEN_ALTERNATE)) + memcpy(&con->def_attr_main, attr, sizeof(*attr)); } SHL_EXPORT @@ -1045,6 +992,8 @@ else con->tab_ruler[i] = false; } + tsm_screen_selection_reset(con); + reset_scrollback_position(con); } SHL_EXPORT @@ -1064,6 +1013,8 @@ if (!(old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) { con->age = con->age_cnt; con->lines = con->alt_lines; + tsm_screen_selection_reset(con); + reset_scrollback_position(con); /* save attributes of main screen when we switch to alt screen */ memcpy(&con->def_attr_main, &con->def_attr, sizeof(con->def_attr)); @@ -1096,6 +1047,8 @@ if ((old & TSM_SCREEN_ALTERNATE) && (flags & TSM_SCREEN_ALTERNATE)) { con->age = con->age_cnt; con->lines = con->main_lines; + tsm_screen_selection_reset(con); + reset_scrollback_position(con); } if ((old & TSM_SCREEN_HIDE_CURSOR) && diff -Nru libtsm-4.4.3/src/tsm/tsm-selection.c libtsm-4.5.0/src/tsm/tsm-selection.c --- libtsm-4.4.3/src/tsm/tsm-selection.c 2026-03-20 09:44:52.000000000 -0400 +++ libtsm-4.5.0/src/tsm/tsm-selection.c 2026-04-21 05:27:37.000000000 -0400 @@ -58,27 +58,30 @@ #include "libtsm.h" #include "libtsm-int.h" #include "shl-llog.h" +#include "shl_dlist.h" #define LLOG_SUBSYSTEM "tsm-selection" static void selection_set(struct tsm_screen *con, struct selection_pos *sel, unsigned int x, unsigned int y) { - struct line *pos; + struct line *line; - sel->line = NULL; - pos = con->sb_pos; + sel->x = x; - while (y && pos) { - --y; - pos = pos->next; + if (!con->sb.pos) { + sel->line = con->lines[y]; + return; } - - if (pos) - sel->line = pos; - - sel->x = x; - sel->y = y; + if (con->sb.pos_num + y >= con->sb.count) { + y -= con->sb.count - con->sb.pos_num; + sel->line = con->lines[y]; + return; + } + line = con->sb.pos; + while (y--) + line = shl_dlist_next(line, &con->sb.list, list); + sel->line = line; } static void word_select(struct tsm_screen *con, @@ -90,10 +93,7 @@ selection_set(con, &con->sel_start, posx, posy); - if (con->sel_start.line) - line = con->sel_start.line; - else - line = con->lines[con->sel_start.y]; + line = con->sel_start.line; if (!line || line->cells[posx].ch == ' ') return; @@ -115,7 +115,8 @@ } } con->sel_start.x = start; - selection_set(con, &con->sel_end, end, posy); + con->sel_end.x = end; + con->sel_end.line = line; con->sel_active = true; } @@ -130,110 +131,57 @@ con->age = con->age_cnt; con->sel_active = false; -} - -SHL_EXPORT -void tsm_screen_selection_start(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - con->sel_active = true; - selection_set(con, &con->sel_start, posx, posy); - memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end)); -} - -SHL_EXPORT -void tsm_screen_selection_target(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con || !con->sel_active) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - selection_set(con, &con->sel_end, posx, posy); -} - -SHL_EXPORT -void tsm_screen_selection_word(struct tsm_screen *con, - unsigned int posx, - unsigned int posy) -{ - if (!con) - return; - - screen_inc_age(con); - /* TODO: more sophisticated ageing */ - con->age = con->age_cnt; - - word_select(con, posx, posy); + con->sel_start.line = NULL; + con->sel_end.line = NULL; } /* calculates the line length from the beginning to the last non zero character */ static unsigned int calc_line_len(struct line *line) { - unsigned int line_len = 0; int i; - for (i = 0; i < line->size; i++) { - if (line->cells[i].ch != 0) { - line_len = i + 1; - } - } - - return line_len; + for (i = line->size - 1; i >= 0; i--) + if (line->cells[i].ch != 0) + return i + 1; + return 0; } /* TODO: tsm_ucs4_to_utf8 expects UCS4 characters, but a cell contains a * tsm-symbol (which can contain multiple UCS4 chars). Fix this when introducing * support for combining characters. */ -static unsigned int copy_line(struct line *line, char *buf, - unsigned int start, unsigned int len) +static unsigned int copy_line(struct tsm_screen *con, struct line *line, char *buf) { - unsigned int i, end; + unsigned int i, start, end; char *pos = buf; int line_len; line_len = calc_line_len(line); - if (start > line_len) { - return 0; - } + start = (con->sel_start.line == line) ? con->sel_start.x : 0; + end = (con->sel_end.line == line) ? con->sel_end.x + 1 : con->size_x; - end = start + len; + if (start > line_len) + return 0; - if (end > line_len) { + if (end > line_len) end = line_len; - } - for (i = start; i < line->size && i < end; ++i) { - if (i < line->size || !line->cells[i].ch) + for (i = start; i < end; i++) { + if (line->cells[i].ch) pos += tsm_ucs4_to_utf8(line->cells[i].ch, pos); else pos += tsm_ucs4_to_utf8(' ', pos); } - pos += tsm_ucs4_to_utf8('\n', pos); - return pos - buf; } -static void swap_selections(struct selection_pos **a, struct selection_pos **b) +static void swap_selections(struct tsm_screen *con) { - struct selection_pos *c; + struct selection_pos c; - c = *a; - *a = *b; - *b = c; + c = con->sel_start; + con->sel_start = con->sel_end; + con->sel_end = c; } /* @@ -241,158 +189,141 @@ * * Start must always point to the top left and end to the bottom right cell */ -static void norm_selection(struct tsm_screen *con, struct selection_pos **start, struct selection_pos **end) +static void norm_selection(struct tsm_screen *con) { - struct line *iter; + int i; + struct selection_pos *start, *end; - if ((*end)->line == NULL && (*end)->y == SELECTION_TOP) { - swap_selections(start, end); + start = &con->sel_start; + end = &con->sel_end; + if (start->line == end->line) { + if (con->sel_start.x > con->sel_end.x) + swap_selections(con); return; } - if ((*start)->line && (*end)->line) { - /* single line selection */ - if ((*start)->line == (*end)->line) { - if ((*start)->x > (*end)->x) { - swap_selections(start, end); - } - - return; - } - - /* - * multi line selection - * - * search from end->line to con->sb_last - * if we find start->line on the way we - * need to change start and end - */ - iter = (*end)->line; - while (iter && iter != con->sb_last) { - if (iter == (*start)->line) { - swap_selections(start, end); - } - - iter = iter->next; - } - + if (is_in_scrollback(&con->sel_start) != is_in_scrollback(&con->sel_end)) { + if (is_in_scrollback(&con->sel_end)) + swap_selections(con); return; } - /* end is in scroll back buffer and start on screen */ - if (!(*start)->line && (*end)->line) { - swap_selections(start, end); + if (is_in_scrollback(&con->sel_start) && is_in_scrollback(&con->sel_end)) { + if (con->sel_start.line->sb_id > con->sel_end.line->sb_id) + swap_selections(con); return; } - /* reorder one-line selection if selection was created right to left */ - if ((*start)->y == (*end)->y) { - if ((*start)->x > (*end)->x) { - swap_selections(start, end); + /* so both are not in scroll back buffer and can't be equal */ + for (i = 0; i < con->size_y; i++) { + if (con->lines[i] == con->sel_end.line) { + swap_selections(con); + return; } + if (con->lines[i] == con->sel_start.line) + return; + } +} +SHL_EXPORT +void tsm_screen_selection_start(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) +{ + if (!con || posx >= con->size_x || posy >= con->size_y) return; - } - /* reorder multi-line selection if selection was created bottom to top */ - if ((*start)->y > (*end)->y) { - swap_selections(start, end); - } + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; + + con->sel_active = true; + selection_set(con, &con->sel_start, posx, posy); + memcpy(&con->sel_end, &con->sel_start, sizeof(con->sel_end)); } -/* - * Counts the lines a normalized selection selects on the scroll back buffer - * - * Does not count the lines selected on the screen - */ -static int selection_count_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end) +SHL_EXPORT +void tsm_screen_selection_target(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) { - struct line *iter; - int count = 0; + if (!con || !con->sel_active || posx >= con->size_x || posy >= con->size_y) + return; - /* Single line selection */ - if (start->line && (start->line == end->line)) { - return 1; - } + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; - iter = start->line; - while (iter) { - count++; + selection_set(con, &con->sel_end, posx, posy); + /* always normalize the selection */ + norm_selection(con); +} - if (iter == con->sb_last) { - break; - } +SHL_EXPORT +void tsm_screen_selection_word(struct tsm_screen *con, + unsigned int posx, + unsigned int posy) +{ + if (!con || posx >= con->size_x || posy >= con->size_y) + return; - iter = iter->next; - } + screen_inc_age(con); + /* TODO: more sophisticated ageing */ + con->age = con->age_cnt; - return count; + word_select(con, posx, posy); } /* - * Counts the lines a normalized selection selects on the screen + * Get the index of a line in the screen * - * Does not count the lines selected in the scroll back buffer + * If the line is in the scroll back buffer, return 0 + * Otherwise, return the index of the line in the screen */ -static int selection_count_lines(struct selection_pos *start, struct selection_pos *end) +static unsigned int get_line_index(struct tsm_screen *con, struct line *line) { - /* Selection only spans lines of the scroll back buffer */ - if (start->line && end->line) { + unsigned int i = 0; + + if (line->sb_id) return 0; - } - return end->y - start->y + 1; + for (i = 0; i < con->size_y; i++) { + if (con->lines[i] == line) + return i; + } + return 0; } -/* - * Calculate the number of selected cells in a line - */-static int calc_selection_line_len_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, struct line *line)
+static struct line *get_next_line(struct tsm_screen *con, struct line *line,
unsigned int *index)
{
- /* one-line selection */
- if (start->line == end->line) {
- return end->x - start->x + 1;
- }
-
- /* first line of a multi-line selection */
- if (line == start->line) {
- return con->size_x - start->x;
- }
+ struct line *next;
- /* last line of a multi-line selection */
- if (line == end->line) {
- return end->x + 1;
+ if (line->sb_id) {
+ next = shl_dlist_next(line, &con->sb.list, list);
+ if (next)
+ return next;
+ *index = 0;
+ return con->lines[0];
+ } else if (*index < con->size_y - 1) {
+ (*index)++;
+ return con->lines[*index];
}
-
- /* every other selection */
- return con->size_x;
+ return NULL;
}
-/*
- * Calculate the number of selected cells in a line
- */
-static int calc_selection_line_len(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, int
line_num)
+static int selection_count_lines(struct tsm_screen *con, struct selection_pos
*start, struct selection_pos *end)
{
- if (!start->line) {
- /* one-line selection */
- if (start->y == end->y) {
- return end->x - start->x + 1;
- }
-
- /* first line of a multi-line selection */
- if (line_num == start->y) {
- return con->size_x - start->x;
- }
- }
+ int count = 1;
+ unsigned int index = get_line_index(con, start->line);
+ struct line *iter;
- /* last line of a multi-line selection */
- if (line_num == end->y) {
- return end->x + 1;
+ iter = start->line;
+ while (iter && iter != end->line) {
+ count++;
+ iter = get_next_line(con, iter, &index);
}
-
- /* every other selection */
- return con->size_x;
+ return count;
}
/*
@@ -404,69 +335,26 @@
return con->size_x * num_lines * 4 + 1;
}
-/*
- * Copy all selected lines from the scroll back buffer
- */
-static int copy_lines_sb(struct tsm_screen *con, struct selection_pos *start, struct selection_pos *end, char *buf, int
pos)
+static int copy_lines(struct tsm_screen *con, struct selection_pos *start,
struct selection_pos *end, char *buf, int pos)
{
+ unsigned int index = get_line_index(con, start->line);
struct line *iter;
- int line_x, line_len;
-
- if (!start->line) {
- return pos;
- }
iter = start->line;
while (iter) {
- line_x = 0;
- if (iter == start->line) {
- line_x = start->x;
- }
-
- line_len = calc_selection_line_len_sb(con, start, end, iter);
- pos += copy_line(iter, &(buf[pos]), line_x, line_len);
-
- if (iter == con->sb_last || iter == end->line) {
+ pos += copy_line(con, iter, &(buf[pos]));
+ if (iter == end->line)
break;
- }
-
- iter = iter->next;
+ iter = get_next_line(con, iter, &index);
}
-
- return pos;
-}
-
-/*
- * Copy all selected lines from the regular screen
- */
-static int copy_lines(struct tsm_screen *con, struct selection_pos *start,
struct selection_pos *end, char *buf, int pos)
-{
- int line_len, line_x, i;
-
- /* selection is scroll back buffer only */
- if (end->line) {
- return pos;
- }
-
- for (i = start->y; i <= end->y; i++) {
- line_len = calc_selection_line_len(con, start, end, i);
-
- line_x = 0;
- if (!start->line && i == start->y) {
- line_x = start->x;
- }
-
- pos += copy_line(con->lines[i], &(buf[pos]), line_x, line_len);
- }
-
return pos;
}
SHL_EXPORT
int tsm_screen_selection_copy(struct tsm_screen *con, char **out)
{
- struct selection_pos *start, *end;
- struct selection_pos start_copy, end_copy;
+ struct selection_pos *start = &con->sel_start;
+ struct selection_pos *end = &con->sel_end;
int buf_size = 0;
int pos = 0;
int total_lines;
@@ -479,36 +367,21 @@
return -ENOENT;
}
- /*
- * copy the selection start and end so we can modify it without affecting
- * the screen in any way
- */
- memcpy(&start_copy, &con->sel_start, sizeof(con->sel_start));
- memcpy(&end_copy, &con->sel_end, sizeof(con->sel_end));
- start = &start_copy;
- end = &end_copy;
-
/* invalid selection */
- if (start->y == SELECTION_TOP && start->line == NULL &&
- end->y == SELECTION_TOP && end->line == NULL) {
+ if (start->line == NULL && end->line == NULL) {
*out = strdup("");
return 0;
}
- norm_selection(con, &start, &end);
-
- if (start->line == NULL && start->y == SELECTION_TOP) {
- if (con->sb_first != NULL) {
- start->line = con->sb_first;
- start->x = 0;
- } else {
- start->y = 0;
- start->x = 0;
- }
+ if (start->line == NULL) {
+ if (!shl_dlist_empty(&con->sb.list))
+ start->line = shl_dlist_first(&con->sb.list, struct line, list);
+ else
+ start->line = con->lines[0];
+ start->x = 0;
}
- total_lines = selection_count_lines_sb(con, start, end);
- total_lines += selection_count_lines(start, end);
+ total_lines = selection_count_lines(con, start, end);
buf_size = calc_line_copy_buffer(con, total_lines);
*out = calloc(buf_size, 1);
@@ -516,7 +389,6 @@
return -ENOMEM;
}
- pos = copy_lines_sb(con, start, end, *out, pos);
pos = copy_lines(con, start, end, *out, pos);
/* remove last line break */
diff -Nru libtsm-4.4.3/src/tsm/tsm-vte.c libtsm-4.5.0/src/tsm/tsm-vte.c
--- libtsm-4.4.3/src/tsm/tsm-vte.c 2026-03-20 09:44:52.000000000 -0400
+++ libtsm-4.5.0/src/tsm/tsm-vte.c 2026-04-21 05:27:37.000000000 -0400
@@ -178,6 +178,13 @@
unsigned int mouse_last_row;
bool bracketed_paste;
+ tsm_vte_bell_cb bell_cb;
+ void *bell_data;
+
+ tsm_vte_led_cb led_cb;
+ void *led_data;
+ unsigned int led_state;
+
uint8_t (*custom_palette_storage)[3];
uint8_t (*palette)[3];
struct tsm_screen_attr def_attr;
@@ -576,6 +583,26 @@
vte->mouse_data = mouse_data;
}
+SHL_EXPORT
+void tsm_vte_set_bell_cb(struct tsm_vte *vte, tsm_vte_bell_cb bell_cb, void
*bell_data)
+{
+ if (!vte)
+ return;
+
+ vte->bell_cb = bell_cb;
+ vte->bell_data = bell_data;
+}
+
+SHL_EXPORT
+void tsm_vte_set_led_cb(struct tsm_vte *vte, tsm_vte_led_cb led_cb, void
*led_data)
+{
+ if (!vte)
+ return;
+
+ vte->led_cb = led_cb;
+ vte->led_data = led_data;
+}
+
static int vte_update_palette(struct tsm_vte *vte)
{
vte->palette = get_palette(vte);
@@ -880,11 +907,8 @@
vte_write(vte, "\x06", 1);
break;
case 0x07: /* BEL */
- /* Sound bell tone */
- /* TODO: I always considered this annying, however, we
- * should at least provide some way to enable it if the
- * user *really* wants it.
- */
+ if (vte->bell_cb)
+ vte->bell_cb(vte, vte->bell_data);
break;
case 0x08: /* BS */
/* Move cursor one position left */
@@ -2100,6 +2124,22 @@
num = vte->csi_argv[0];
tsm_screen_repeat_char(vte->con, num);
break;
+ case 'q': /* DECLL - Load LEDs */
+ num = vte->csi_argv[0];
+ if (num <= 0) {
+ vte->led_state = 0;
+ } else if (num == 1) {
+ vte->led_state |= TSM_VTE_LED_SCROLL_LOCK;
+ } else if (num == 2) {
+ vte->led_state |= TSM_VTE_LED_NUM_LOCK;
+ } else if (num == 3) {
+ vte->led_state |= TSM_VTE_LED_CAPS_LOCK;
+ } else {
+ break;
+ }
+ if (vte->led_cb)
+ vte->led_cb(vte, vte->led_state, vte->led_data);
+ break;
default:
llog_debug(vte, "unhandled CSI sequence %c", data);
}
diff -Nru libtsm-4.4.3/test/test_screen.c libtsm-4.5.0/test/test_screen.c
--- libtsm-4.4.3/test/test_screen.c 2026-03-20 09:44:52.000000000 -0400
+++ libtsm-4.5.0/test/test_screen.c 2026-04-21 05:27:37.000000000 -0400
@@ -257,6 +257,34 @@
ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3);
+ tsm_screen_newline(screen);
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 4);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 4);
+
+ tsm_screen_newline(screen);
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 5);
+
+ tsm_screen_sb_up(screen, 2);
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 3);
+
+ tsm_screen_newline(screen);
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 2);
+
+ r = tsm_screen_resize(screen, 5, 3);
+ ck_assert_int_eq(r, 0);
+
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 5);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0);
+
+ r = tsm_screen_resize(screen, 5, 5);
+ ck_assert_int_eq(r, 0);
+
+ ck_assert_int_eq(tsm_screen_sb_get_line_count(screen), 3);
+ ck_assert_int_eq(tsm_screen_sb_get_line_pos(screen), 0);
+
tsm_screen_unref(screen);
screen = NULL;
}
diff -Nru libtsm-4.4.3/test/test_selection.c libtsm-4.5.0/test/test_selection.c
--- libtsm-4.4.3/test/test_selection.c 2026-03-20 09:44:52.000000000 -0400
+++ libtsm-4.5.0/test/test_selection.c 2026-04-21 05:27:37.000000000 -0400
@@ -203,9 +203,7 @@
tsm_screen_selection_target(screen, 14, 39);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 39);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 39);
/* force the selected text to scroll up */
tsm_screen_newline(screen);
@@ -217,9 +215,7 @@
tsm_screen_newline(screen);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 32);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 32);
r = tsm_screen_selection_copy(screen, &str);
ck_assert_ptr_ne(NULL, str);
@@ -267,7 +263,6 @@
str = NULL;
/* Select "This is a copy test\nfor a selection" from top left and to
bottom right copy it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 0, 2);
tsm_screen_selection_target(screen, 14, 3);
@@ -278,7 +273,6 @@
str = NULL;
/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy
it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 41, 4);
tsm_screen_selection_target(screen, 3, 1);
@@ -321,9 +315,7 @@
tsm_screen_selection_target(screen, 5, 39);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 37);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 39);
/* force the selected text to scroll up */
tsm_screen_newline(screen);
@@ -335,9 +327,7 @@
tsm_screen_newline(screen);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 30);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 32);
r = tsm_screen_selection_copy(screen, &str);
ck_assert_ptr_ne(NULL, str);
@@ -462,9 +452,7 @@
tsm_screen_selection_target(screen, 14, 0);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 0);
/* force the selected text to scroll up */
for (int i = 0; i < 40; i++) {
@@ -472,10 +460,8 @@
}
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, -1);
ck_assert_ptr_ne(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, -1);
ck_assert_ptr_ne(screen->sel_end.line, NULL);
r = tsm_screen_selection_copy(screen, &str);
@@ -496,10 +482,8 @@
tsm_screen_selection_target(screen, 14, 0);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_ptr_ne(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 0);
ck_assert_ptr_ne(screen->sel_end.line, NULL);
tsm_screen_newline(screen);
@@ -507,10 +491,8 @@
tsm_screen_newline(screen);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_ptr_ne(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 0);
ck_assert_ptr_ne(screen->sel_end.line, NULL);
r = tsm_screen_selection_copy(screen, &str);
@@ -549,26 +531,16 @@
tsm_screen_selection_target(screen, 14, 0);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, 0);
/* force the selected text to scroll up */
for (i = 0; i < 40; i++) {
tsm_screen_newline(screen);
}
- /*
- * sel_start.y == -1, sel_start.line == NULL
- * sel_end.y == -1, sel_end.line == NULL
- *
- * => Invalid selection
- */
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, -1);
ck_assert_ptr_eq(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 14);
- ck_assert_int_eq(screen->sel_end.y, -1);
ck_assert_ptr_eq(screen->sel_end.line, NULL);
r = tsm_screen_selection_copy(screen, &str);
@@ -630,7 +602,6 @@
str = NULL;
/* Select "This is a copy test\nfor a selection" from top left and to
bottom right copy it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 0, 2);
tsm_screen_selection_target(screen, 14, 3);
@@ -641,10 +612,12 @@
str = NULL;
/* Select all text excluding the first 3 spaces and the trailing '-' chars from bottom right to top left and copy
it */
- tsm_screen_reset(screen); tsm_screen_selection_start(screen, 41, 4); tsm_screen_selection_target(screen, 3, 1); + ck_assert_int_eq(screen->sel_start.x, 3); + ck_assert_int_eq(screen->sel_end.x, 41); + r = tsm_screen_selection_copy(screen, &str); ck_assert_ptr_ne(NULL, str);ck_assert_str_eq("Hello World!\nThis is a copy test\nfor a selection with multiple lines.\nAll of them are on screen (not in the sb).", str);
@@ -652,7 +625,6 @@
str = NULL;
/* Select from scroll back buffer and the screen from top left to bottom
right and copy it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 0, 4);
tsm_screen_selection_target(screen, 18, 6);
@@ -663,10 +635,12 @@
str = NULL;
/* Select from scroll back buffer and the screen from bottom right to top
left and copy it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 18, 6);
tsm_screen_selection_target(screen, 0, 4);
+ ck_assert_int_eq(screen->sel_start.x, 0);
+ ck_assert_int_eq(screen->sel_end.x, 18);
+
r = tsm_screen_selection_copy(screen, &str);
ck_assert_ptr_ne(NULL, str);
ck_assert_str_eq("All of them are on screen (not in the sb).------\nText not in
SB\nMore Text not in SB", str);
@@ -674,7 +648,6 @@
str = NULL;
/* Select from scroll back buffer and the screen from bottom right to top
left and copy it */
- tsm_screen_reset(screen);
tsm_screen_selection_start(screen, 8, 6);
tsm_screen_selection_target(screen, 7, 4);
@@ -719,9 +692,7 @@
tsm_screen_selection_target(screen, 5, 2);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 2);
/* force the selected text to scroll into the sb */
for (i = 0; i < 40; i++) {
@@ -729,10 +700,8 @@
}
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, -1);
ck_assert_ptr_ne(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, -1);
ck_assert_ptr_ne(screen->sel_end.line, NULL);
ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);
@@ -754,19 +723,15 @@
tsm_screen_selection_target(screen, 5, 2);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 0);
tsm_screen_newline(screen);
tsm_screen_newline(screen);
tsm_screen_newline(screen);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_ptr_ne(screen->sel_start.line, NULL);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 0);
ck_assert_ptr_ne(screen->sel_end.line, NULL);
ck_assert_ptr_ne(screen->sel_start.line, screen->sel_end.line);
@@ -810,9 +775,7 @@
tsm_screen_selection_target(screen, 5, 2);
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, 0);
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 2);
/* force the selected text to scroll up */
for (i = 0; i < 39; i++) {
@@ -820,11 +783,9 @@
}
ck_assert_int_eq(screen->sel_start.x, 3);
- ck_assert_int_eq(screen->sel_start.y, -1);
- ck_assert_ptr_eq(screen->sel_start.line, NULL);
+ ck_assert(!is_in_scrollback(&screen->sel_start));
ck_assert_int_eq(screen->sel_end.x, 5);
- ck_assert_int_eq(screen->sel_end.y, 0);
- ck_assert_ptr_eq(screen->sel_end.line, NULL);
+ ck_assert(!is_in_scrollback(&screen->sel_end));
r = tsm_screen_selection_copy(screen, &str);
ck_assert_ptr_ne(NULL, str);
@@ -837,6 +798,96 @@
}
END_TEST
+static void check_sb_pos(struct tsm_screen *screen)
+{
+ struct line *line = shl_dlist_first(&screen->sb.list, struct line, list);
+ int count = 0;
+
+ if (!screen->sb.pos) {
+ ck_assert_int_eq(screen->sb.pos_num, screen->sb.count);
+ return;
+ }
+
+ ck_assert_int_le(screen->sb.pos_num, screen->sb.count);
+
+ while (line && line != screen->sb.pos) {
+ count++;
+ line = shl_dlist_next(line, &screen->sb.list, list);
+ }
+
+ ck_assert_int_eq(count, screen->sb.pos_num);
+}
+
+static void write_random_string(struct tsm_screen *screen, int count)
+{
+ char str[201];
+ int len = rand() % 100;
+ int i, c;
+
+ for (c = 0; c < count; c++) {
+ for (i = 0; i < len; i++)
+ str[i] = ' ' + rand() % 95;
+ str[len] = '\0';
+ write_string(screen, str);
+ tsm_screen_newline(screen);
+ }
+}
+
+START_TEST(test_screen_robustness)
+{
+ struct tsm_screen *screen;
+ int sb_size = 50;
+ int size_x, size_y;
+ int r, i, j;
+ char *str = NULL;
+
+
+ srand(0x12345678);
+
+ r = tsm_screen_new(&screen, NULL, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_screen_resize(screen, 80, 40);
+ ck_assert_int_eq(r, 0);
+
+ tsm_screen_set_max_sb(screen, sb_size);
+
+ write_random_string(screen, 60);
+
+ check_sb_pos(screen);
+
+ for (i = 0; i < 100; i++) {
+ size_x = 1 + rand() % 100;
+ size_y = 1 + rand() % 100;
+ r = tsm_screen_resize(screen, size_x, size_y);
+ ck_assert_int_eq(r, 0);
+ check_sb_pos(screen);
+ tsm_screen_sb_up(screen, rand() % sb_size);
+ check_sb_pos(screen);
+ tsm_screen_sb_down(screen, rand() % sb_size);
+ check_sb_pos(screen);
+ tsm_screen_selection_start(screen, rand() % size_x, rand() % size_y);
+ tsm_screen_selection_target(screen, rand() % size_x, rand() % size_y);
+ for (j = 0; j < 20; j++) {
+ r = tsm_screen_selection_copy(screen, &str);
+ ck_assert_int_ge(r, 0);
+ if (str) {
+ free(str);
+ str = NULL;
+ }
+ tsm_screen_sb_up(screen, sb_size );
+ check_sb_pos(screen);
+ tsm_screen_sb_down(screen, rand() % sb_size);
+ check_sb_pos(screen);
+ }
+ write_random_string(screen, rand() % 100);
+ }
+
+ tsm_screen_unref(screen);
+ screen = NULL;
+}
+END_TEST
+
TEST_DEFINE_CASE(misc)
TEST(test_screen_copy_incomplete)
TEST(test_screen_copy_one_cell)
@@ -850,6 +901,7 @@
TEST(test_screen_copy_lines_sb)
TEST(test_screen_copy_lines_sb_scrolled)
TEST(test_screen_copy_lines_sb_scrolled_cut_off)
+ TEST(test_screen_robustness)
TEST_END_CASE
TEST_DEFINE(
diff -Nru libtsm-4.4.3/test/test_vte.c libtsm-4.5.0/test/test_vte.c
--- libtsm-4.4.3/test/test_vte.c 2026-03-20 09:44:52.000000000 -0400
+++ libtsm-4.5.0/test/test_vte.c 2026-04-21 05:27:37.000000000 -0400
@@ -228,6 +228,12 @@
input = "\e]11;?;12;?\x07";
strcpy(expected_output, "\e]11;rgb:1111/2323/3535\x07");
checked_vte_input(vte, input, strlen(input));
+
+ tsm_vte_unref(vte);
+ vte = NULL;
+
+ tsm_screen_unref(screen);
+ screen = NULL;
}
END_TEST
@@ -295,6 +301,12 @@
tsm_vte_input(vte, input, strlen(input));
ck_assert_ptr_eq(write_buffer, write_buffer_p);
storing_write_cb_reset();
+
+ tsm_vte_unref(vte);
+ vte = NULL;
+
+ tsm_screen_unref(screen);
+ screen = NULL;
}
END_TEST
@@ -458,6 +470,97 @@
}
END_TEST
+static bool bell_cb_called = false;
+
+static void bell_cb(struct tsm_vte *vte, void *data)
+{
+ bool *flag = data;
+ ck_assert_ptr_ne(vte, NULL);
+ if (flag)
+ *flag = true;
+ bell_cb_called = true;
+}
+
+START_TEST(test_vte_bell)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ tsm_vte_set_bell_cb(vte, bell_cb, NULL);
+
+ bell_cb_called = false;
+ tsm_vte_input(vte, "\x07", 1);
+ ck_assert(bell_cb_called);
+
+ /* BEL inside an OSC sequence acts as the ST terminator, not as a bell */
+ bell_cb_called = false;
+ tsm_vte_input(vte, "\033]0;title\x07", 10);
+ ck_assert(!bell_cb_called);
+
+ tsm_vte_unref(vte);
+ vte = NULL;
+
+ tsm_screen_unref(screen);
+ screen = NULL;
+}
+END_TEST
+
+START_TEST(test_vte_bell_no_cb)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ /* BEL without a registered callback must not crash */
+ tsm_vte_input(vte, "\x07", 1);
+
+ tsm_vte_unref(vte);
+ vte = NULL;
+
+ tsm_screen_unref(screen);
+ screen = NULL;
+}
+END_TEST
+
+START_TEST(test_vte_bell_data)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ bool flag = false;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ tsm_vte_set_bell_cb(vte, bell_cb, &flag);
+
+ tsm_vte_input(vte, "\x07", 1);
+ ck_assert(flag);
+
+ tsm_vte_unref(vte);
+ vte = NULL;
+
+ tsm_screen_unref(screen);
+ screen = NULL;
+}
+END_TEST
+
TEST_DEFINE_CASE(misc)
TEST(test_vte_init)
TEST(test_vte_null)
@@ -470,10 +573,117 @@
TEST(test_vte_csi_cursor_up_down)
TEST_END_CASE
+TEST_DEFINE_CASE(bell)
+ TEST(test_vte_bell)
+ TEST(test_vte_bell_no_cb)
+ TEST(test_vte_bell_data)
+TEST_END_CASE
+
+static unsigned int led_cb_leds = 0;
+static bool led_cb_called = false;
+
+static void led_cb(struct tsm_vte *vte, unsigned int leds, void *data)
+{
+ unsigned int *out = data;
+ ck_assert_ptr_ne(vte, NULL);
+ if (out)
+ *out = leds;
+ led_cb_leds = leds;
+ led_cb_called = true;
+}
+
+START_TEST(test_vte_led_decll)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ tsm_vte_set_led_cb(vte, led_cb, NULL);
+
+ /* CSI 1 q — turn on Scroll Lock LED */
+ led_cb_called = false;
+ tsm_vte_input(vte, "\033[1q", 4);
+ ck_assert(led_cb_called);
+ ck_assert_uint_eq(led_cb_leds, TSM_VTE_LED_SCROLL_LOCK);
+
+ /* CSI 3 q — turn on Caps Lock LED (cumulative) */
+ led_cb_called = false;
+ tsm_vte_input(vte, "\033[3q", 4);
+ ck_assert(led_cb_called);
+ ck_assert_uint_eq(led_cb_leds, TSM_VTE_LED_SCROLL_LOCK |
TSM_VTE_LED_CAPS_LOCK);
+
+ /* CSI 0 q — clear all LEDs */
+ led_cb_called = false;
+ tsm_vte_input(vte, "\033[0q", 4);
+ ck_assert(led_cb_called);
+ ck_assert_uint_eq(led_cb_leds, 0);
+
+ tsm_vte_unref(vte);
+ tsm_screen_unref(screen);
+}
+END_TEST
+
+START_TEST(test_vte_led_no_cb)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ /* DECLL without a registered callback must not crash */
+ tsm_vte_input(vte, "\033[1q", 4);
+
+ tsm_vte_unref(vte);
+ tsm_screen_unref(screen);
+}
+END_TEST
+
+START_TEST(test_vte_led_data)
+{
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ unsigned int out = 0;
+ int r;
+
+ r = tsm_screen_new(&screen, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ r = tsm_vte_new(&vte, screen, write_cb, NULL, log_cb, NULL);
+ ck_assert_int_eq(r, 0);
+
+ tsm_vte_set_led_cb(vte, led_cb, &out);
+
+ tsm_vte_input(vte, "\033[2q", 4);
+ ck_assert_uint_eq(out, TSM_VTE_LED_NUM_LOCK);
+
+ tsm_vte_unref(vte);
+ tsm_screen_unref(screen);
+}
+END_TEST
+
+TEST_DEFINE_CASE(led)
+ TEST(test_vte_led_decll)
+ TEST(test_vte_led_no_cb)
+ TEST(test_vte_led_data)
+TEST_END_CASE
+
// clang-format off
TEST_DEFINE(
TEST_SUITE(vte,
TEST_CASE(misc),
+ TEST_CASE(bell),
+ TEST_CASE(led),
TEST_END
)
)
OpenPGP_0xC293E7B461825ACE.asc
Description: OpenPGP public key
OpenPGP_signature.asc
Description: OpenPGP digital signature

